the many facets of code reuse in javascript
DESCRIPTION
Talk given at the ThoughtWorks XConf event in Australia - April/2012TRANSCRIPT
The many facets of code reuse in JavaScript
Leonardo Borges@leonardo_borgeshttp://www.leonardoborges.comhttp://www.thoughtworks.com
Highly opinionated talk ahead
JavaScript is all about Objects
Not classes
Not access modifiers
Just Objects
Objects
• An unordered collection of properties• Arrays are Objects• Functions are Objects• Regular Expressions are Objects
...you catch my drift
Ok, I lied.
There’s more to JavaScript than just Objects
The Prototype
The Prototype
Object
- prototype- __proto__
The Prototype
Object
- prototype- __proto__
The Prototype
myObject
- prototype- __proto__
Object
- prototype- __proto__
The Prototype
myObject
- prototype- __proto__
Object
- prototype- __proto__
The Prototype
myObject
- prototype- __proto__
Object
- prototype- __proto__
undefined
The Prototype
myObject
- prototype- __proto__
Object
- prototype- __proto__
undefined function Empty() {}
But how do you create Objects?
Object Literals
var person = { firstName: "Leonardo", lastName: "Borges", age: 29};
Object.create
• Specified by the ECMAScript 5th edition • Modern browsers such as Chrome and Firefox already implement it
Object.create
• Specified by the ECMAScript 5th edition • Modern browsers such as Chrome and Firefox already implement it
var anotherPerson = Object.create(person, { age: { value: 50 }});anotherPerson.age; //50
Object.createif (typeof Object.create !== 'function') { Object.create = function(o) { var F = function() {}; F.prototype = o; return new F(); };}
var anotherPerson = Object.create(person);
Functions
• First class objects• Linked to Function.prototype• Can be used as constructors f(x)
Functions
Function
- prototype- __proto__
Functions
Function
- prototype- __proto__
function Empty() {}
Functions
myFunction
- prototype- __proto__
Function
- prototype- __proto__
function Empty() {}
Functions
myFunction
- prototype- __proto__
Function
- prototype- __proto__
function Empty() {}
Functions
myFunction
- prototype- __proto__
Function
- prototype- __proto__
function Empty() {}
Function calls
Implicit arguments:
• this, the current object• arguments, an array containing all values passed into the function call
function f(a, b, c) { console.log(a); console.log(b); console.log(c); console.log(this); console.log(arguments);}f(1, 2, 3);
function f(a, b, c) { console.log(a); console.log(b); console.log(c); console.log(this); console.log(arguments);}f(1, 2, 3);
// 1// 2// 3// DOMWindow// [1, 2, 3]
function f(a, b, c) { console.log(a); console.log(b); console.log(c); console.log(this); console.log(arguments);}f(1, 2, 3);
// 1// 2// 3// DOMWindow// [1, 2, 3]
The value of this changes depending on how the function is called.
Calling functions: as methodsvar person = { firstName: "Leonardo", lastName: "Borges", fullName: function() { return this.firstName + " " + this.lastName; }};
person.fullName();
Calling functions: as methodsvar person = { firstName: "Leonardo", lastName: "Borges", fullName: function() { return this.firstName + " " + this.lastName; }};
person.fullName();
this is bound to person
Calling functions: as, err, functionsthis.firstName = "Leo";this.lastName = "Borges";
function fullName() { return this.firstName + " " + this.lastName;}
fullName(); //Leo Borgesthis; //DOMWindow
Calling functions: as, err, functionsthis.firstName = "Leo";this.lastName = "Borges";
function fullName() { return this.firstName + " " + this.lastName;}
fullName(); //Leo Borgesthis; //DOMWindow
this is bound to the global object
Calling functions: using apply
Allows the value of this to be changed upon calling:
Calling functions: using apply
var anotherPerson = { firstName: "Johnny", lastName: "Cash"};
person.fullName.apply(anotherPerson); //Johnny Cash
Allows the value of this to be changed upon calling:
Calling functions: using apply
var anotherPerson = { firstName: "Johnny", lastName: "Cash"};
person.fullName.apply(anotherPerson); //Johnny Cash
Allows the value of this to be changed upon calling:
this is bound to anotherPerson
Calling functions: constructors
//constructor functionvar F = function() {};
var obj = new F();
What does new do?
Creates a new object, obj
Assigns F’s public prototype to the obj internal prototype
Binds this to obj
var obj = {};
obj.__proto__ === F.prototype// true
this === obj// true
Don’t use new
Don’t use new
• No built-in checks to prevent constructors from being called as regular functions• If you forget new, this will be bound to the global object
But I want to
new workaround//constructor functionvar F = function() { if (!(this instanceof F)) { return new F(); }};
var obj = new F();var obj = F();//both previous statements are now equivalent
new workaround//constructor functionvar F = function() { if (!(this instanceof F)) { return new F(); }};
var obj = new F();var obj = F();//both previous statements are now equivalent
You can see how cumbersome this can get
Closures
var Person = function(name) { this.name = name; return { getName: function() { return this.name; } };};var leo = new Person("leo");leo.getName();
Closures
var Person = function(name) { this.name = name; return { getName: function() { return this.name; } };};var leo = new Person("leo");leo.getName();
Can you guess what this line returns?
undefined
Closures
var Person = function(name) { this.name = name; return { getName: function() { return this.name; } };};var leo = new Person("leo");leo.getName();
Closures
Bound to the person object
var Person = function(name) { this.name = name; return { getName: function() { return this.name; } };};var leo = new Person("leo");leo.getName();
Closures
Bound to the person object
Bound to the object literal
var Person = function(name) { this.name = name; return { getName: function() { return this.name; } };};var leo = new Person("leo");leo.getName();
Closures
Allows a function to access variables outside it’s scope
Closuresvar Person = function(name) { this.name = name; var that = this; return { getName: function() { return that.name; } };};
var leo = new Person("leo");leo.getName(); // “leo”
Closuresvar Person = function(name) { this.name = name; var that = this; return { getName: function() { return that.name; } };};
var leo = new Person("leo");leo.getName(); // “leo”
{getName is now a closure: it closes over that
Sharing behaviour
Pseudoclassical inheritance//constructor functionvar Aircraft = function(name){ this.name = name;};Aircraft.prototype.getName = function() { return this.name;}Aircraft.prototype.fly = function() { return this.name + ": Flying...";}
Pseudoclassical inheritance//constructor functionvar Aircraft = function(name){ this.name = name;};Aircraft.prototype.getName = function() { return this.name;}Aircraft.prototype.fly = function() { return this.name + ": Flying...";}
var cirrus = new Aircraft("Cirrus SR22"); cirrus.getName(); //"Cirrus SR22"cirrus.fly(); //"Cirrus SR22: Flying..."
Pseudoclassical inheritancevar Jet = function(name){ this.name = name;};Jet.prototype = new Aircraft();Jet.prototype.fly = function() { return this.name + ": Flying a jet...";}
Pseudoclassical inheritancevar Jet = function(name){ this.name = name;};Jet.prototype = new Aircraft();Jet.prototype.fly = function() { return this.name + ": Flying a jet...";}
var boeing = new Jet("Boeing 747");boeing.getName(); //"Boeing 747"boeing.fly(); //"Boeing 747: Flying a jet..."
Prototypal inheritance
• Objects inherit directly from other objects• Sometimes referred to as differential inheritance
Prototypal inheritancevar myAircraft = { name: "Cirrus SR22", getName: function() { return this.name; }, fly: function() { return this.name + ": Flying..."; }};
myAircraft.getName(); //"Cirrus SR22"myAircraft.fly(); //"Cirrus SR22: Flying..."
Prototypal inheritance
var myJet = Object.create(myAircraft);myJet.name = "Boeing 747";myJet.fly = function() { return this.name + ": Flying a jet...";}
myJet.getName(); //"Boeing 747"myJet.fly(); //"Boeing 747: Flying a jet..."
Weaknesses• Lack of private members - all properties are public• No easy access to super• In the pseudoclassical pattern, forgetting new will breakyour code
Weaknesses• Lack of private members - all properties are public• No easy access to super• In the pseudoclassical pattern, forgetting new will breakyour code
Strengths• Using the prototype is the fastest way to create objects when compared to closures• In practice it will only matter if you’re creating thousands of objects
Functional inheritance
var aircraft = function(spec) { var that = {}; that.getName = function() { return spec.name; }; that.fly = function() { return spec.name + ": Flying..."; }; return that;};
Functional inheritance
var aircraft = function(spec) { var that = {}; that.getName = function() { return spec.name; }; that.fly = function() { return spec.name + ": Flying..."; }; return that;};
Members declared here are private
Functional inheritancevar myAircraft = aircraft({ name: "Cirrus SR22" });myAircraft.getName(); //"Cirrus SR22"myAircraft.fly(); //"Cirrus SR22: Flying..."
Functional inheritancevar myAircraft = aircraft({ name: "Cirrus SR22" });myAircraft.getName(); //"Cirrus SR22"myAircraft.fly(); //"Cirrus SR22: Flying..."
myAircraft.that; //undefined
Functional inheritancevar jet = function(spec) { var that = aircraft(spec); that.fly = function() { return spec.name + ": Flying a jet..."; }; return that;};
Functional inheritancevar jet = function(spec) { var that = aircraft(spec); that.fly = function() { return spec.name + ": Flying a jet..."; }; return that;};
that is now an aircraft
Functional inheritancevar jet = function(spec) { var that = aircraft(spec); that.fly = function() { return spec.name + ": Flying a jet..."; }; return that;};
var myJet = jet({ name: "Boeing 747" }); myJet.getName(); //"Boeing 747"myJet.fly(); //"Boeing 747: Flying a jet..."
that is now an aircraft
But I want to reuse my fly function
Implementing super
Object.prototype.super = function(fName) { var that = this; var f = that[fName];
return function() { return f.apply(that, arguments); };};
Revisiting jetvar jet = function(spec) { var that = aircraft(spec), superFly = that.super("fly"); that.fly = function() { return superFly() + "a frickin' jet!"; }; return that;};
var myJet = jet({ name: "Boeing 747"});myJet.fly(); //"Boeing 747: Flying...a frickin' jet!"
Weaknesses• Consumes more memory: every object created allocates new function objects as necessary• In practice it will only matter if you’re creating thousands of objects
Functional inheritance
Weaknesses• Consumes more memory: every object created allocates new function objects as necessary• In practice it will only matter if you’re creating thousands of objects
Strengths• It’s conceptually simpler than pseudoclassical inheritance• Provides true private members• Provides a way of working with super (albeit verbose)• Avoids the new workaround since new isn’t used at all
Functional inheritance
Both Prototypal and Functional patterns are powerful
Both Prototypal and Functional patterns are powerful
I’d avoid the pseudoclassical path
But wait! Inheritance isn’t the only way to share behaviour
An alternative to inheritance:Mixins
var utils = {};utils.enumerable = { reduce: function(acc, f) { for (var i = 0; i < this.length; i++) { acc = f(acc, this[i]); } return acc; }};
An alternative to inheritance:Mixins
var utils = {};utils.enumerable = { reduce: function(acc, f) { for (var i = 0; i < this.length; i++) { acc = f(acc, this[i]); } return acc; }};
Sitck it into a module so as to avoid clobbering the global namespace
Mixins - implementing extends
utils.extends = function(dest,source) { for (var prop in source) { if (source.hasOwnProperty(prop)) { dest[prop] = source[prop]; } }};
Mixins extending Array.prototype
utils.extends(Array.prototype, utils.enumerable);
Mixins extending Array.prototype
utils.extends(Array.prototype, utils.enumerable);
[1,2,3].reduce(0, function(acc,item) { acc += item; return acc;}); // 6
Going apeshit
functional
Partial function application
Partial function application
var sum = function(a, b) { return a + b;};
Partial function application
var sum = function(a, b) { return a + b;};
var inc = utils.partial(sum, 1);
Partial function application
var sum = function(a, b) { return a + b;};
var inc = utils.partial(sum, 1);
inc(8); //9
Partial function application
Partial function application
var times = function(a, b) { return a * b;}
Partial function application
var times = function(a, b) { return a * b;}
var double = utils.partial(times, 2);
Partial function application
var times = function(a, b) { return a * b;}
var double = utils.partial(times, 2);
double(10); //20
Partial function application
utils.partial = function(f) { var sourceArgs = Array.prototype.slice.apply(arguments, [1]); return function() { var actualArgs = Array.prototype.concat.apply(sourceArgs, arguments); return f.apply(null, actualArgs); };};
Partial function application
utils.partial = function(f) { var sourceArgs = Array.prototype.slice.apply(arguments, [1]); return function() { var actualArgs = Array.prototype.concat.apply(sourceArgs, arguments); return f.apply(null, actualArgs); };};
f**k yeah
Function composition:Reversing a string
Function compositionutils.split = function(str) { return String.prototype.split.apply(str, [""]);};
utils.reverse = function(array) { return Array.prototype.reverse.apply(array);};
utils.join = function(array) { return Array.prototype.join.apply(array, [""]);};
Function composition
var reverseStr = utils.comp(utils.split, utils.reverse, utils.join);
Function composition
var reverseStr = utils.comp(utils.split, utils.reverse, utils.join);
reverseStr("leonardo"); //odranoel
Function composition
utils.comp = function() { var functions = Array.prototype.slice.apply(arguments, [0]); return function() { var result = functions.reduce(arguments, function(r, f) { return [f.apply(null, r)] }); return result[0];
};};
Function composition
utils.comp = function() { var functions = Array.prototype.slice.apply(arguments, [0]); return function() { var result = functions.reduce(arguments, function(r, f) { return [f.apply(null, r)] }); return result[0];
};};
f**k yeah
Thankfully implements these - and much more - for us!
Bottom line
Bottom line
• Embrace JavaScript’s true prototypal nature
Bottom line
• Embrace JavaScript’s true prototypal nature• Use mixins, prototypal and functional inheritance to dispense with classes and the new keyword
Bottom line
• Embrace JavaScript’s true prototypal nature• Use mixins, prototypal and functional inheritance to dispense with classes and the new keyword• Use higher order functions, partial application and function composition as elegant alternatives to promote code reuse
Bottom line
• Embrace JavaScript’s true prototypal nature• Use mixins, prototypal and functional inheritance to dispense with classes and the new keyword• Use higher order functions, partial application and function composition as elegant alternatives to promote code reuse• Above all, understand each pattern’s strengths and weaknesses
Thanks!
Questions?
Leonardo Borges@leonardo_borges
http://www.leonardoborges.comhttp://www.thoughtworks.com
References
• JavaScript: the Good Parts (http://amzn.to/zY04ci)• Test-Driven JavaScript Development (http://amzn.to/yHCexS) • Secrets of the JavaScript Ninja (http://bit.ly/wOS4x2)• Closures wiki article (http://bit.ly/xF2OfP)• Underscore.js - functional programming for JS (http://bit.ly/JLkIbm)• Presentation source code (http://bit.ly/HU8kL6)
Leonardo Borges@leonardo_borgeshttp://www.leonardoborges.comhttp://www.thoughtworks.com