The many facets of code reuse in JavaScript

3,351 views

Published on

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

Published in: Technology
2 Comments
5 Likes
Statistics
Notes
No Downloads
Views
Total views
3,351
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
57
Comments
2
Likes
5
Embeds 0
No embeds

No notes for slide

The many facets of code reuse in JavaScript

  1. 1. The many facets of code reuse in JavaScript Leonardo Borges @leonardo_borges http://www.leonardoborges.com http://www.thoughtworks.com
  2. 2. Highly opinionated talk ahead
  3. 3. JavaScript is all about Objects
  4. 4. Not classes
  5. 5. Not access modifiers
  6. 6. Just Objects
  7. 7. Objects• An unordered collection of properties• Arrays are Objects• Functions are Objects• Regular Expressions are Objects ...you catch my drift
  8. 8. Ok, I lied.
  9. 9. There’s more to JavaScript than just Objects
  10. 10. The Prototype
  11. 11. The Prototype Object - prototype - __proto__
  12. 12. The Prototype Object - prototype - __proto__
  13. 13. The Prototype myObject Object- prototype - prototype- __proto__ - __proto__
  14. 14. The Prototype myObject Object- prototype - prototype- __proto__ - __proto__
  15. 15. The Prototype myObject Object - prototype - prototype - __proto__ - __proto__undefined
  16. 16. The Prototype myObject Object - prototype - prototype - __proto__ - __proto__undefined function Empty() {}
  17. 17. But how do you create Objects?
  18. 18. Object Literals var person = {     firstName: "Leonardo",     lastName: "Borges",     age: 29 };
  19. 19. Object.create• Specified by the ECMAScript 5th edition• Modern browsers such as Chrome and Firefox already implement it
  20. 20. 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
  21. 21. Object.createif (typeof Object.create !== function) {    Object.create = function(o) {        var F = function() {};        F.prototype = o;        return new F();    };}var anotherPerson = Object.create(person);
  22. 22. Functions f(x)• First class objects• Linked to Function.prototype• Can be used as constructors
  23. 23. Functions Function - prototype - __proto__
  24. 24. Functions Function - prototype - __proto__ function Empty() {}
  25. 25. FunctionsmyFunction Function- prototype - prototype- __proto__ - __proto__ function Empty() {}
  26. 26. FunctionsmyFunction Function- prototype - prototype- __proto__ - __proto__ function Empty() {}
  27. 27. FunctionsmyFunction Function- prototype - prototype- __proto__ - __proto__ function Empty() {}
  28. 28. Function calls Implicit arguments:• this, the current object• arguments, an array containing all values passedinto the function call
  29. 29. function f(a, b, c) {    console.log(a);    console.log(b);    console.log(c);    console.log(this);    console.log(arguments);}f(1, 2, 3);
  30. 30. 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]
  31. 31. 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.
  32. 32. Calling functions: as methods var person = {     firstName: "Leonardo",     lastName: "Borges",     fullName: function() {         return this.firstName + " " + this.lastName;     } }; person.fullName();
  33. 33. Calling functions: as methods var person = {     firstName: "Leonardo",     lastName: "Borges",     fullName: function() {         return this.firstName + " " + this.lastName;     } }; person.fullName(); this is bound to person
  34. 34. Calling functions: as, err, functions this.firstName = "Leo"; this.lastName = "Borges"; function fullName() {     return this.firstName + " " + this.lastName; } fullName(); //Leo Borges this; //DOMWindow
  35. 35. Calling functions: as, err, functions this.firstName = "Leo"; this.lastName = "Borges"; function fullName() {     return this.firstName + " " + this.lastName; } fullName(); //Leo Borges this; //DOMWindow this is bound to the global object
  36. 36. Calling functions: using apply Allows the value of this to be changed upon calling:
  37. 37. Calling functions: using apply Allows the value of this to be changed upon calling: var anotherPerson = {     firstName: "Johnny",     lastName: "Cash" }; person.fullName.apply(anotherPerson); //Johnny Cash
  38. 38. Calling functions: using apply Allows the value of this to be changed upon calling: var anotherPerson = {     firstName: "Johnny",     lastName: "Cash" }; person.fullName.apply(anotherPerson); //Johnny Cash this is bound to anotherPerson
  39. 39. Calling functions: constructors //constructor function var F = function() {}; var obj = new F();
  40. 40. What does new do?Creates a new object, obj var obj = {};Assigns F’s public prototype to the obj.__proto__ === F.prototype// trueobj internal prototype this === obj// trueBinds this to obj
  41. 41. Don’t use new
  42. 42. Don’t use new• No built-in checks to prevent constructors frombeing called as regular functions• If you forget new, this will be bound to theglobal object
  43. 43. But I want to
  44. 44. 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
  45. 45. 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
  46. 46. Closuresvar Person = function(name) {    this.name = name;    return {        getName: function() {            return this.name;        }    };};var leo = new Person("leo");leo.getName();
  47. 47. Closuresvar 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?
  48. 48. undefined
  49. 49. Closuresvar Person = function(name) {    this.name = name;    return {        getName: function() {            return this.name;        }    };};var leo = new Person("leo");leo.getName();
  50. 50. Closures var Person = function(name) {     this.name = name;     return {         getName: function() {             return this.name;Bound to the person object         }     }; }; var leo = new Person("leo"); leo.getName();
  51. 51. Closures var Person = function(name) {     this.name = name;     return {         getName: function() {             return this.name;Bound to the person object         }     }; }; var leo = new Person("leo"); Bound to the object literal leo.getName();
  52. 52. ClosuresAllows a function to access variables outside it’s scope
  53. 53. Closuresvar Person = function(name) {    this.name = name;    var that = this;    return {        getName: function() {            return that.name;        }    };};var leo = new Person("leo");leo.getName(); // “leo”
  54. 54. Closures var Person = function(name) {     this.name = name;     var that = this; {     return { getName is now a         getName: function() {closure: it closes over that             return that.name;         }     }; }; var leo = new Person("leo"); leo.getName(); // “leo”
  55. 55. Sharing behaviour
  56. 56. Pseudoclassical inheritance //constructor function var Aircraft = function(name){     this.name = name; }; Aircraft.prototype.getName = function() {     return this.name; } Aircraft.prototype.fly = function() {     return this.name + ": Flying..."; }
  57. 57. Pseudoclassical inheritance //constructor function var 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..."
  58. 58. Pseudoclassical inheritance var Jet = function(name){     this.name = name; }; Jet.prototype = new Aircraft(); Jet.prototype.fly = function() {     return this.name + ": Flying a jet..."; }
  59. 59. Pseudoclassical inheritance var 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..."
  60. 60. Prototypal inheritance• Objects inherit directly from other objects• Sometimes referred to as differential inheritance
  61. 61. 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..."
  62. 62. Prototypal inheritancevar 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..."
  63. 63. Weaknesses• Lack of private members - all properties are public• No easy access to super• In the pseudoclassical pattern, forgetting new will breakyour code
  64. 64. 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 createobjects when compared to closures• In practice it will only matter if you’re creatingthousands of objects
  65. 65. Functional inheritancevar aircraft = function(spec) {    var that = {};    that.getName = function() {        return spec.name;    };    that.fly = function() {        return spec.name + ": Flying...";    };    return that;};
  66. 66. Functional inheritance Members declaredvar aircraft = function(spec) { here are private    var that = {};    that.getName = function() {        return spec.name;    };    that.fly = function() {        return spec.name + ": Flying...";    };    return that;};
  67. 67. Functional inheritancevar myAircraft = aircraft({ name: "Cirrus SR22" });myAircraft.getName(); //"Cirrus SR22"myAircraft.fly(); //"Cirrus SR22: Flying..."
  68. 68. Functional inheritancevar myAircraft = aircraft({ name: "Cirrus SR22" });myAircraft.getName(); //"Cirrus SR22"myAircraft.fly(); //"Cirrus SR22: Flying..."myAircraft.that; //undefined
  69. 69. Functional inheritancevar jet = function(spec) {    var that = aircraft(spec);    that.fly = function() {        return spec.name + ": Flying a jet...";    };    return that;};
  70. 70. Functional inheritance that is now anvar jet = function(spec) { aircraft    var that = aircraft(spec);    that.fly = function() {        return spec.name + ": Flying a jet...";    };    return that;};
  71. 71. Functional inheritance that is now anvar jet = function(spec) { aircraft    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..."
  72. 72. But I want to reuse my fly function
  73. 73. Implementing superObject.prototype.super = function(fName) {    var that = this;    var f = that[fName];    return function() {        return f.apply(that, arguments);    };};
  74. 74. 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!"
  75. 75. Functional inheritance Weaknesses• Consumes more memory: every object created allocatesnew function objects as necessary• In practice it will only matter if you’re creating thousandsof objects
  76. 76. Functional inheritance Weaknesses• Consumes more memory: every object created allocatesnew function objects as necessary• In practice it will only matter if you’re creating thousandsof 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
  77. 77. Both Prototypal and Functional patterns are powerful
  78. 78. Both Prototypal and Functional patterns are powerful I’d avoid the pseudoclassical path
  79. 79. But wait! Inheritance isn’t the only way to share behaviour
  80. 80. 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;     } };
  81. 81. An alternative to inheritance: Mixins Sitck it into a module so as to avoid clobbering var utils = {}; the global namespace utils.enumerable = {     reduce: function(acc, f) {         for (var i = 0; i < this.length; i++) {             acc = f(acc, this[i]);         }         return acc;     } };
  82. 82. Mixins - implementing extends utils.extends = function(dest,source) {     for (var prop in source) {         if (source.hasOwnProperty(prop)) {             dest[prop] = source[prop];         }     } };
  83. 83. Mixinsextending Array.prototype utils.extends(Array.prototype, utils.enumerable);
  84. 84. Mixinsextending Array.prototype utils.extends(Array.prototype, utils.enumerable); [1,2,3].reduce(0, function(acc,item) {     acc += item;     return acc; }); // 6
  85. 85. Going apeshit functional
  86. 86. Partial function application
  87. 87. Partial function application var sum = function(a, b) {     return a + b; };
  88. 88. Partial function application var sum = function(a, b) {     return a + b; }; var inc = utils.partial(sum, 1);
  89. 89. Partial function application var sum = function(a, b) {     return a + b; }; var inc = utils.partial(sum, 1); inc(8); //9
  90. 90. Partial function application
  91. 91. Partial function application var times = function(a, b) {     return a * b; }
  92. 92. Partial function application var times = function(a, b) {     return a * b; } var double = utils.partial(times, 2);
  93. 93. Partial function application var times = function(a, b) {     return a * b; } var double = utils.partial(times, 2); double(10); //20
  94. 94. Partial function applicationutils.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);    };};
  95. 95. Partial function applicationutils.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);    };}; a h ye * k f *
  96. 96. Function composition: Reversing a string
  97. 97. 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, [""]);};
  98. 98. Function compositionvar reverseStr = utils.comp(utils.split, utils.reverse, utils.join);
  99. 99. Function compositionvar reverseStr = utils.comp(utils.split, utils.reverse, utils.join);reverseStr("leonardo"); //odranoel
  100. 100. Function compositionutils.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];    };};
  101. 101. Function compositionutils.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];    }; a h}; ye * k f *
  102. 102. Thankfully implements these - and much more - for us!
  103. 103. Bottom line
  104. 104. Bottom line• Embrace JavaScript’s true prototypal nature
  105. 105. Bottom line• Embrace JavaScript’s true prototypal nature• Use mixins, prototypal and functional inheritance to dispense withclasses and the new keyword
  106. 106. Bottom line• Embrace JavaScript’s true prototypal nature• Use mixins, prototypal and functional inheritance to dispense withclasses and the new keyword• Use higher order functions, partial application and functioncomposition as elegant alternatives to promote code reuse
  107. 107. Bottom line• Embrace JavaScript’s true prototypal nature• Use mixins, prototypal and functional inheritance to dispense withclasses and the new keyword• Use higher order functions, partial application and functioncomposition as elegant alternatives to promote code reuse• Above all, understand each pattern’s strengths and weaknesses
  108. 108. Thanks!Questions? Leonardo Borges @leonardo_borgeshttp://www.leonardoborges.com http://www.thoughtworks.com
  109. 109. 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_borges http://www.leonardoborges.com http://www.thoughtworks.com

×