Created by Hook Studios
"An entity which supports all the operations generally available to other entities” such as
// Function declaration. Okay but can't be nested within non-function blocks
// The whole thing gets hoisted at compile time. (beware)
function double(x) {
return x * x;
}
// Function expression, first class! 'var double = undefined' gets hoisted
// but the definition itself does not. (so chill)
var double = function(x) {
return x * x;
}
// function can be stored in an array, first class!
var myArray = [5, 'socks', {a: 3}, double, false, Nan, []];
var nums = [1, 1, 2, 3, 5, 8];
// functon can be passed as an argument, first class!
var sum = nums.map(double); // [1, 1, 4, 9, 25, 64]
function or(p1, p2) {
return function(x) {
return p1(x) || p2(x);
}
}
function negative(x) {
return x < 0;
}
function positive(x) {
return x > 0;
}
var nonzero = or(negative, positive);
console.log(nonzero(-5)); //true
console.log(nonzero(0)); //false
console.log(nonzero(5)); //true
let array = [1, 2, 3, 4, 5];
// pure
array.slice(0, 2);
console.log(`[${array}]`); // [1,2,3,4,5]
array.slice(0, 2);
console.log(`[${array}]`); // [1,2,3,4,5]
array.slice(0, 2);
console.log(`[${array}]`); // [1,2,3,4,5]
// impure
array.splice(0, 2);
console.log(`[${array}]`); // [3,4,5]
array.splice(0, 2);
console.log(`[${array}]`); // [5]
array.splice(0, 2);
console.log(`[${array}]`); // []
Closure: A loveable concept
let add = function(a){
return function(b){
return a + b;
}
};
// the variable 'a' is enclosed and
// is only accessible to the returning function.
let add10 = add(10);
let result = add10(20);
console.log(result);
let array = [0, 1, 2, 3, 4, 5];
// a basic map function
array.map((element, index) => {
console.log(`Element: ${element}
`);
console.log(`Index: ${index}
`);
});
// for loop comparison
for(let i = 0; i < array.length; i++) {
console.log(`Element: ${array[i]}
`);
console.log(`Index: ${i}
`);
};
// index is optional!
// a basic map function
array.map((element) => {
console.log(`Element: ${element}
`);
});
const map = (fn, value) => value.map(fn);
Array.filter() calls a provided callback once for each element in the array and constructs a new array of all the values for which the callback returns ‘true’ or a value that coerces to true. Only invoked for indexes which have assigned values.
var customers = [
{
name: 'John',
phone: '(877)4432-12'
},
{
name: 'Anne',
phone: '323-847-1590'
},
{
name: 'Daphne',
phone: '+1 23 5461'
},
{
name: 'Howard',
phone: '13607034562'
}
]
var reachable = function() {
var valid = [];
for (var i = 0; i < customers.length; i++) {
var sanitizedPhone = customers[i].phone.replace(/\D/g, '');
if(sanitizedPhone.length === 10 || sanitizedPhone.length === 11) {
valid.push(customers[i]);
}
}
return valid;
};
var reachable = customers.filter(function(person) {
var sanitizedPhone = person.phone.slice().replace(/\D/g, '');
return sanitizedPhone.length === 10 || sanitizedPhone.length === 11;
});
var hasValidPhoneNumber = function(person) {
var sanitizedPhone = person.phone.slice().replace(/\D/g, '');
return sanitizedPhone.length === 10 || sanitizedPhone.length === 11;
}
var reachable = customers.filter(hasValidPhoneNumber);
arr.reduce(callback [, initialValue]);
var numbers = [1,2,3,4];
// sum up all the values of an array
numbers.reduce(function(x,y) {
return x+y
}, 0); // 10
// es6
numbers.reduce ((x, y) => x + y); // 10
// find the largest number
numbers.reduce((x, y) => Math.max(x, y), 0); // 4
Have you heard of Ramda or currying?
Not quite....
// normal - regular addition
let add = (a, b) => a + b;
// let add = function(a, b) {
// return a + b;
// };
console.log(`Normal JS Invocation: ${add(1, 2)}`); // 3
console.log(`Normal JS Invocation: ${add(1, 2, 'IGNORE ME')}`); // 3
console.log(`Normal JS Invocation: ${add(1)}`); // NaN
// curried - curried addition
// a curried function is one where multiple arguemnts are described by a
// series of one-argument functions.
let addCurried = a => b => a + b;
// let addCurried = function(a) {
// return function(b) {
// return a + b;
// };
// };
let cAdd = addCurried(1);
console.log(`Curried: ${cAdd(2)}`); // 3
console.log(`Curried: ${cAdd(2, 'IGNORE ME')}`); // 3
// ramda - makes stuff easier
let ramdaCurried = R.curry(add);
let rAdd = ramdaCurried(1);
console.log(`Curried: ${rAdd(2)}`); // 3
console.log(`Curried: ${rAdd(2, 'IGNORE ME')}`); // 3
let objects = [{ id: 1 }, { id: 2 }, { id: 3 }];
console.log(`w/o Curry: [${objects.map(o => o.id)}]`); // [1, 2, 3]
let get = R.curry((property, object) => {
return object[property];
});
console.log(`w/ Curry: [${objects.map(get('id'))}]`); // [1, 2, 3]
// Take it even further!
let map = R.curry((fn, value) => value.map(fn));
let getIDS = map(get('id'));
console.log(`Super Ultra Functional: [${getIDS(objects)}]`); // [1, 2, 3]
Not to be confused with funk-sters
// Note that the type signature for functors looks like so
// my functor :: (a -> b) -> f a -> f b
We already have one functor: map(). It grabs the values within the container, an array, and applies a function to it.
var nums = [1, 4, 9];
nums.map(Math.sqrt); // [1, 2, 3]
However, we'll need to write it as a global function and not as a method of the array object. This will allow us to write cleaner, safer code later on.
// map :: (a -> b) -> [a] -> [b]
var map = function(f, a) {
return arr(a).map(func(f));
}
// function that combines two functions together:
// run function 'f' on the result of function 'g'
var compose = function(f, g) {
return function(x) {
return f(g(x));
};
};
// ES6 hyper-terse equivalent
const compose = (f, g) => (x) => f(g(x));
const upperCase = (str) => str.toUpperCase();
const trim = (str) => str.replace(/^\s+I\s+$/g, '');
const replaceIWithBang = (str) => str.replace(/[i]/g, '!');
const labelMaker = compose(upperCase, trim);
// Ramda's built-in compose method takes any number of functions and
// will return them right-to-left
const coolLabelMaker = R.compose(upperCase, replaceIWithBang, trim);
let labels = [' iconic ', 'high five ', ' bim bap'].map(labelMaker);
let coolLabels = [' iconic ', 'high five ', ' bim bap'].map(coolLabelMaker);
console.log(labels); // [ 'ICONIC', 'HIGH FIVE', 'BIM BAP']
console.log(coolLabels); // ["!CON!C", "H!GH F!VE", "B!M BAP"]
Generating a valid HTTP request using an oauth signature
'use strict';
var createjs = require('createjs');
var canvas;
var stage;
var exportRoot;
// set canvas id
var Canvas = function() {
canvas = document.getElementById('you-line');
};
// initialize canvas animation
Canvas.prototype.init = function() {
exportRoot = new lib02.Youtube_Beacon05_Lily_970x250();
stage = new createjs.Stage(canvas);
stage.addChild(exportRoot);
stage.update();
stage.enableDOMEvents(false);
};
// start canvas animation
Canvas.prototype.start = function() {
canvas.style.opacity = 1;
createjs.Ticker.setFPS(lib02.properties.fps);
createjs.Ticker.addEventListener('tick', stage);
};
module.exports = Canvas; //29 lines
'use strict';
import createjs from 'createjs';
export default function() {
const youLine = document.getElementById('you-line');
const exportRoot = new lib02.Youtube_Beacon05_Lily_970x250();
const stage = new createjs.Stage(youLine);
const canvas = Object.create({});
canvas.init = () => {
stage.addChild(exportRoot);
stage.update();
stage.enableDOMEvents(false);
};
canvas.start = () => {
youLine.style.opacity = 1;
createjs.Ticker.setFPS(lib02.properties.fps);
createjs.Ticker.addEventListener('tick', stage);
};
return canvas;
}; //23 lines - 20% reduction
'use strict';
function Loader( manifest ) {
this.manifest = manifest;
this.loadItems = 0;
this.loadItemsTotal = 0;
this.loadComplete = false;
this.consoleObject = {
loaded: 0.0
};
manifest.forEach(function(item) {
this._loadImage(item.src);
}.bind(this));
this._loadScript('js/vendor.js');
}
Loader.prototype._loadImage = function( path ) {
this.consoleObject[path] = false;
var img = new Image();
img.addEventListener( 'load', this._onLoad.bind( this, path ) );
img.src = path;
this.loadItemsTotal++;
};
Loader.prototype._loadScript = function( path ) {
this.consoleObject[path] = false;
global.Enabler.loadScript( global.Enabler.getUrl( path ), this._onLoad.bind( this, path ) );
this.loadItemsTotal++;
};
Loader.prototype._onLoad = function ( id ) {
this.loadItems++;
this.consoleObject[id] = true;
this.consoleObject.loaded = Math.round( ( this.loadItems / this.loadItemsTotal ) * 100 ) + '%';
if ( this.loadItemsTotal === this.loadItems ) {
this._onLoadComplete();
}
};
Loader.prototype._onLoadComplete = function () {
if (this.loadComplete === true) {
return;
}
this.loadComplete = true;
this._loadScript('js/main.build.js');
};
module.exports = Loader; //50 lines of code
'use strict';
export default function(manifest) {
//Loads all the images in the manifest
const loadImage = function(path) {
return new Promise((resolve, reject) => {
let img = new Image();
img.addEventListener('load', resolve);
img.addEventListener('error', reject);
img.src = path.src;
});
};
//loads vendor and main.build scripts, individually
const loadScript = function(path) {
return new Promise((resolve, reject) => {
global.Enabler.loadScript(global.Enabler.getUrl(path), resolve);
});
};
const loadImagePromise = manifest.map(loadImage);
const loadVendor = loadScript('js/vendor.js');
const loadMain = () => loadScript('js/main.build.js');
//After all the images have loaded, then load in vendor, lastly load mainbuild.js
Promise.all(loadImagePromise.concat(loadVendor)).then(loadMain);
}; //24 lines of code - 52% reduction.
'use strict';
var config = require('../config');
var Loader = require('./Loader');
// set up youtube iframe api, add function to global scope
var Bootstrap = function () {
this.tag = document.createElement('script');
this.tag.src = 'https://www.youtube.com/iframe_api';
this.firstScriptTag = document.getElementsByTagName('script')[0];
this.firstScriptTag.parentNode.insertBefore(this.tag, this.firstScriptTag);
window.onYouTubeIframeAPIReady = function() {
this.onYouTubeLoaded();
}.bind(this);
};
// once youtube is loaded, initialize enabler
Bootstrap.prototype.onYouTubeLoaded = function () {
if (Enabler.isInitialized()) {
this.onInitialize();
} else {
Enabler.addEventListener(studio.events.StudioEvent.INIT, this.onInitialize.bind(this));
}
};
// once the page is initialized, check that the page is loaded and fire pageLoadedHandler
Bootstrap.prototype.onInitialize = function () {
if (Enabler.isPageLoaded()) {
this.pageLoadedHandler();
} else {
Enabler.addEventListener(studio.events.StudioEvent.PAGE_LOADED, this.pageLoadedHandler.bind(this));
}
};
// make ad visible
Bootstrap.prototype.pageLoadedHandler = function () {
if (Enabler.isVisible()) {
this.adVisibleHandler();
} else {
Enabler.addEventListener(studio.events.StudioEvent.VISIBLE, this.adVisibleHandler.bind(this));
}
};
// create ad
Bootstrap.prototype.adVisibleHandler = function () {
var loader = new Loader(config.manifest);
};
module.exports = Load; //59 lines of code
'use strict';
import { config } from './config';
import Loader from './core/Loader';
// Load the YouTube API, insert the script tag and assign the window object.
const loadYTApi = () => {
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
return new Promise( (resolve, reject) => {
window.onYouTubeIframeAPIReady = resolve;
});
};
// Returns a promise that resolves if the method is true, otherwise assigns an event listener.
const enablerCheck = (method, state) => {
return new Promise( (resolve, reject) => {
return method() ? resolve() : Enabler.addEventListener(state, resolve);
});
};
// Load the manifest once the Ad is visible.
const adVisibleHandler = () => {
const loader = Loader(config.manifest);
};
// Enabler promises for initialize, load and visible states.
const initPromise = enablerCheck(Enabler.isInitialized.bind(Enabler), studio.events.StudioEvent.INIT);
const loadPromise = enablerCheck(Enabler.isPageLoaded.bind(Enabler), studio.events.StudioEvent.PAGE_LOADED);
const visiblePromise = enablerCheck(Enabler.isVisible.bind(Enabler), studio.events.StudioEvent.VISIBLE);
// If everything is a-ok, show the Ad.
Promise.all([loadYTApi(), initPromise, loadPromise, visiblePromise]).then(adVisibleHandler);
//39 lines of code - 34% reduction of code
'use strict';
var TweenMax = require('TweenMax');
var AnimationController = function () {
// selectors
this.player = document.querySelector('#player');
this.animationWrap = document.querySelector('#animation');
this.loader = document.querySelector('#loading');
this.whiteBox = document.querySelector('#white-box');
this.lillyText = document.querySelector('#lilly-text');
this.fansText = document.querySelector('#fans-text');
this.slash = document.querySelector('#slash');
this.underlay = document.querySelector('#underlay');
this.lozenge = document.querySelector('#lozenge');
// booleans
this.endOnce = true;
this.init();
};
// initialize controller, build all timelines.
AnimationController.prototype.init = function() {
this.globalTimeline = new TimelineMax({paused: true});
this.globalTimeline.add(this.tl1(), 0.0);
this.globalTimeline.add(this.tl2(), 21.25);
return this.globalTimeline;
};
// timeline 0 - loading wipe animation
AnimationController.prototype.tl1 = function () {
this.tl1 = new TimelineMax();
this.tl1.to([this.underlay, this.loader], 0.4, {autoAlpha: 0, ease: Power1.easeOut});
return this.tl1;
};
// timeline 4 - last zoom in of branding
AnimationController.prototype.tl2 = function () {
this.tl2 = new TimelineMax();
this.tl2.fromTo(this.animationWrap, 3.225, {x: '-200%',
autoAlpha: 1,
scaleX: 10,
scaleY: 10},
{x: '0%',
scaleX: 1,
scaleY: 1,
ease: Power3.easeOut});
return this.tl2;
};
// make player visible, set animation wrap and fire tl0
AnimationController.prototype.start = function () {
this.player.style.opacity = 1;
this.globalTimeline.play();
};
// play animation
AnimationController.prototype.play = function () {
this.globalTimeline.play();
};
// seek animation
AnimationController.prototype.seek = function(time) {
this.globalTimeline.seek(time);
};
// pause animation
AnimationController.prototype.pause = function () {
this.globalTimeline.pause();
};
// kill all animations, end banner
AnimationController.prototype.endState = function () {
if (this.endOnce) {
TweenMax.killAll(true, true, true, true);
TweenMax.set(this.animationWrap, {autoAlpha: 1});
this.endOnce = false;
}
};
module.exports = AnimationController; //84 lines of code
'use strict';
import TweenMax from 'TweenMax';
export default function() {
// selectors
const player = document.querySelector('#player');
const animationWrap = document.querySelector('#animation');
const loader = document.querySelector('#loading');
const whiteBox = document.querySelector('#white-box');
const lillyText = document.querySelector('#lilly-text');
const fansText = document.querySelector('#fans-text');
const slash = document.querySelector('#slash');
const underlay = document.querySelector('#underlay');
const lozenge = document.querySelector('#lozenge');
// timeline
let timeline = null;
// booleans
let endOnce = true;
const animation = Object.create({});
animation.init = () => {
timeline = new TimelineMax({paused: true});
timeline.add(fadeOutLoader(), 0.0);
timeline.add(animateBranding(), 21.25);
return timeline;
};
function fadeOutLoader() {
const tl = new TimelineMax();
tl.to([underlay, loader], 0.4, {autoAlpha: 0, ease: Power1.easeOut});
return tl;
};
function animateBranding() {
const tl = new TimelineMax();
tl.fromTo(animationWrap, 3.225, {x: '-200%',
autoAlpha: 1,
scaleX: 10,
scaleY: 10},
{x: '0%',
scaleX: 1,
scaleY: 1,
ease: Power3.easeOut});
return tl;
};
animation.start = () => {
player.style.opacity = 1;
timeline.play();
};
animation.play = () => timeline.play();
animation.seek = (time) => timeline.seek(time);
animation.pause = () => timeline.pause();
animation.endState = () => {
if (endOnce) {
TweenMax.killAll(true, true, true, true);
TweenMax.set(animationWrap, {autoAlpha: 1});
endOnce = false;
}
};
return animation;
}; //70 lines of code - 16% reduction
Let's talk Frameworks
React
var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
Seconds Elapsed: {this.state.secondsElapsed}
);
}
});
ReactDOM.render(<Timer />, mountNode);
Deku
import {element} from 'deku'
function render (component) {
let {props,state} = component
return
}
function afterMount(component, el, setState) {
setState({ mounted: true })
}
export default { render, afterMount }
import {tree,render,element} from 'deku'
import Button from './button'
var app = tree()
render(app, document.body)
export let Button = {
render({props, state}) {
return
}
}
Vue
-
{{ todo.text }}
new Vue({
el: '#app',
data: {
newTodo: '',
todos: [
{ text: 'Add some todos' }
]
},
methods: {
addTodo: function () {
var text = this.newTodo.trim()
if (text) {
this.todos.push({ text: text })
this.newTodo = ''
}
},
removeTodo: function (index) {
this.todos.splice(index, 1)
}
}
})
Let's talk Libraries
As the years go, Internet Explorer 9 has faded into the past and native ES5 methods are expected for most applications. So the role of functional programming libraries has shifted from a compatibility layer to adding new, foundational methods for functional programming.
Underscore
LoDash
Ramda
Why bother?
Functional Programming encourages functions that:
You drastically reduce the likelihood of bugs

Debugging becomes easier
Changing the code is less painful

var memoize = function(f) {
//create a cache
var cache = {};
return function() {
var arg_str = JSON.stringify(arguments);
//If we already have the answer in our cache, do nothing
//Otherwise, store the result of this function call in our cache
cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
return cache[arg_str];
};
};
function fibonacci(n) {
return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
function betterFibonacci = memoize(function(n) {
return (n === 0 || n === 1) ? n : betterFibonacci(n - 1) + betterFibonacci(n - 2);
});
var ITERATIONS = 42;
console.time('non-memoized');
console.log(fibonacci(ITERATIONS));
console.timeEnd('non-memoized'); // 3788ms
console.time('memoized');
console.log(betterFibonacci(ITERATIONS));
console.timeEnd('memoized'); // 1ms !!!
Functional Programming vs. Object Oriented Programming
“The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”-Joe Armstrong
Functional programming is a style that emphasizes and enables the writing of smarter code, which minimizes complexity and increases modularity.
JavaScript provides an excellent medium for this approach.