the many facets of code reuse in javascript

Post on 20-May-2015

2.306 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Talk given at the ThoughtWorks XConf event in Australia - April/2012

TRANSCRIPT

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

top related