Slide 1

Slide 1 text

Select Ways to Harness the Power of JavaScript based largely on Effective JavaScript by David Herman

Slide 2

Slide 2 text

Always Declare Local Variables function score(player) { sum = 0; for (i = 0, n = player.levels.length; i < n; i++) { sum += player.levels[i].score; } return sum; } function averageScore(players) { sum = 0; for (i = 0, n = players.length; i < n; i++) { sum += score(players[i]); } return sum / n; }

Slide 3

Slide 3 text

Minimize Use of the Global Object var i, n, sum; // globals function score(player) { sum = 0; for (i = 0, n = player.levels.length; i < n; i++) { sum += player.levels[i].score; } return sum; } var i, n, sum; // same globals as score! function averageScore(players) { sum = 0; for (i = 0, n = players.length; i < n; i++) { sum += score(players[i]); } return sum / n; }

Slide 4

Slide 4 text

Minimize Use of the Global Object function score(player) { var i, n, sum; sum = 0; for (i = 0, n = player.levels.length; i < n; i++) { sum += player.levels[i].score; } return sum; } function averageScore(players) { var i, n, sum; sum = 0; for (i = 0, n = players.length; i < n; i++) { sum += score(players[i]); } return sum / n; }

Slide 5

Slide 5 text

Basic JavaScript Scope Chains var x = 10; (function foo() { var y = 20; (function bar() { var z = 30; console.log(x + y + z); })(); })();

Slide 6

Slide 6 text

Global Object is Root of Scope Chain this; // [global object] foo; // ReferenceError this.foo; // undefined foo = "global foo"; this.foo; // "global foo" var bar = "global bar"; this.bar= "changed"; bar; // "changed" typeof hasOwnProperty; // "function"

Slide 7

Slide 7 text

Avoid with var w = 20, y = 30; (function foo() { var w = 40, x = 100; with (objWithNondeterministicProps) { console.log(w, x, y, z); // ?, ?, ?, ? } })();

Slide 8

Slide 8 text

Avoid with var w = 20, y = 30; (function foo() { var w = 40, x = 100; with (objWithNondeterministicProps) { console.log(w, x, y); // ?, ?, ? z = 40 } console.log(z); // ? })();

Slide 9

Slide 9 text

Avoid with Object.prototype.x = 10; var w = 20, y = 30; // "x" is from `Object.prototype` because // the global object inherits from it console.log(x); // 10 (function foo() { var w = 40, x = 100; // "x" is from `Object.prototype` // because {z: 50} inherits from it with ({z: 50}) { console.log(w, x, y, z); // 40, 10, 30, 50 } console.log(x, w); // 100, 40 console.log(global.w); // 20 })();

Slide 10

Slide 10 text

Get Comfortable with Closures function sandwichMaker() { var magicIngredient = "peanut butter"; return function make(filling) { return magicIngredient + " and " + filling; }; } var f = sandwichMaker(); f("jelly"); // "peanut butter and jelly" f("bananas"); // "peanut butter and bananas" f("marshmallows"); // "peanut butter and marshmallows"

Slide 11

Slide 11 text

Get Comfortable with Closures function sandwichMaker(magicIngredient) { return function make(filling) { return magicIngredient + " and " + filling; }; } var hamAnd = sandwichMaker("ham"); hamAnd("cheese"); // "ham and cheese" hamAnd("mustard"); // "ham and mustard" var turkeyAnd = sandwichMaker("turkey"); turkeyAnd("Swiss"); // "turkey and Swiss" turkeyAnd("Provolone"); // "turkey and Provolone"

Slide 12

Slide 12 text

Get Comfortable with Closures function baz() { var x = 1; return { foo: function foo() { return ++x; }, bar: function bar() { return --x; } }; } var closures = baz(); closures.foo(); // 2 closures.bar(); // 1

Slide 13

Slide 13 text

Understand Variable Hoisting

Slide 14

Slide 14 text

Understand Variable Hoisting var outerErr; try { throw "error"; } catch(innerErr) { typeof outerErr; // "undefined" typeof innerErr; // "string" outerErr = innerErr; } typeof outerErr; // "string" typeof innerErr; // "undefined"

Slide 15

Slide 15 text

Use IIFEs to Create Local Scopes function wrapElements(a) { var result = []; for (var i = 0, n = a.length; i < n; i++) { result[i] = function() { return a[i]; }; } return result; } var array = [10, 20, 30, 40, 50]; var wrapped = wrapElements(array); wrapped[0](); // undefined wrapped[1](); // undefined // etc. array.push(60); wrapped[0](); // 60 wrapped[1](); // 60 // etc.

Slide 16

Slide 16 text

Use IIFEs to Create Local Scopes function wrapElements(a) { var result = []; for (var i = 0, n = a.length; i < n; i++) { (function(k) { result[i] = function() { return a[k]; }; })(i); } return result; } var array = [10, 20, 30, 40, 50]; var wrapped = wrapElements(array); wrapped[0](); // 10 wrapped[1](); // 20 wrapped[2](); // 30 // etc.

Slide 17

Slide 17 text

Understand the Differences Between FDs, NFEs, and FEs // Function Declarations typeof a; // "function" function a(){} function b(){ typeof a; // "function" typeof b; // "function" typeof c; // "function" typeof d; // "function" function c(){} // ... function d(){} }

Slide 18

Slide 18 text

Understand the Differences Between FDs, NFEs, and FEs // Named Function Expressions (function a(){}); void function a(){}; fn(function a(){}); typeof a; // "undefined" typeof f; // "undefined" typeof g; // "undefined" var f = function g(){ typeof f; // "function" typeof g; // "function" f === g; // true }; typeof f; // "function" typeof g; // "undefined"

Slide 19

Slide 19 text

Understand the Differences Between FDs, NFEs, and FEs // Function Expressions (function(){}); void function(){}; 0, function(){}; +function(){}; fn(function(){}); typeof f; // "undefined" var f = function(){ typeof f; // "function" }; typeof f; // "function"

Slide 20

Slide 20 text

Beware of Block-Local FDs function f() { return "global"; } function test(x) { var result = []; if (x) { function f() { return "local"; } // illegal syntax result.push(f()); } result.push(f()); return result; } test(true); // undefined behaviour test(false); // undefined behaviour

Slide 21

Slide 21 text

Prefer Indirect eval to Direct eval var x = "global"; function testDirect() { var x = "local"; return eval("x"); // direct eval } testDirect(); // "local" function testIndirect() { var x = "local"; var f = eval; return f("x"); // indirect eval } testIndirect(); // "global" (0,eval)("string"); // terse indirect eval

Slide 22

Slide 22 text

Use call/apply to Call Methods With a Given Context // goal: call `f` with `o` as the context o.temporary = f; // what if o.temporary already existed? var result = o.temporary(arg1, arg2, arg3); delete o.temporary; // what if o.temporary already existed? // instead, use `call` f.call(o, arg1, arg2, arg3);

Slide 23

Slide 23 text

Use apply to Call Methods With a Dynamic Number of Arguments // mean is a variadic function function mean(){ var sum = [].reduce.call(arguments, function(memo, n){ return memo + n; }, 0); return sum / arguments.length; } mean(1, 2, 3); // 2 mean(1); // 1 mean(3, 1, 4, 1, 5, 9, 2, 6, 5); // 4 mean(2, 7, 1, 8, 2, 8, 1, 8); // 4.625 // goal: compute the mean of the values in this list var array = [2, 7, 1, 8, 2, 8, 1, 8]; mean.apply(null, array); // 4.625

Slide 24

Slide 24 text

Use apply to Call Methods With a Dynamic Number of Arguments // this becomes a little more difficult with constructors function NumberContainer(){ this.nums = [].map.call(arguments, function(n){ return +n; }); } NumberContainer.prototype.mean = function(){ return [].reduce.call(arguments, function(memo, n){ return memo + n; }, 0) / arguments.length; }; new NumberContainer(1, 2, 3).mean(); // 2 // goal: compute the mean of the values in this list var array = [2, 7, 1, 8, 2, 8, 1, 8]; var o = Object.create(NumberContainer.prototype); NumberContainer.apply(o, array); o.mean(); // 4.625

Slide 25

Slide 25 text

Understand the Differences Between Functions, Methods, and Constructors // functions don't mention `this` function hello(username) { return "hello, " + username; } hello("Keyser Söze"); // "hello, Keyser Söze" // "methods" mention `this` var o = { hello: function() { return "hello, " + this.username; }, username: "Hans Gruber" }; o.hello(); // "hello, Hans Gruber" // constructors are meant to be used with `new` and // usually have a meaningful prototype property function Cow(){} Cow.prototype = Object.create(Animal.prototype); new Cow;

Slide 26

Slide 26 text

Understand the Differences Between Functions, Methods, and Constructors // watch your constructor return values var returnValue; function A(){ return returnValue; } Object(returnValue) === returnValue; // false new A instanceof A; // true returnValue = null; Object(returnValue) === returnValue; // false new A instanceof A; // true returnValue = "string"; Object(returnValue) === returnValue; // false new A instanceof A; // true returnValue = {}; Object(returnValue) === returnValue; // true new A instanceof A; // false

Slide 27

Slide 27 text

Understand the Difference between prototype, getPrototypeOf, and __proto__ ■ C.prototype is used to establish the [[Prototype]] of objects created by new C. ■ Object.getPrototypeOf(o) is the standard ES5 mechanism for retrieving o’s [[Prototype]] object. ■ o.__proto__ is a non-standard mechanism for retrieving or re-assigning o’s [[Prototype]] object.

Slide 28

Slide 28 text

Understand the Difference between prototype, getPrototypeOf, and __proto__ function User(name, passwordHash) { this.name = name; this.passwordHash = passwordHash; } User.prototype.checkPassword = function(password) { return hash(password) === this.passwordHash; }; var u = new User("sfalken", "0ef33ae791068ec64b502d6cb0191387"); // two `u.[[Prototype]] === User.prototype` tests u instanceof User; // true Object.getPrototypeOf(u) === User.prototype; // true // avoid __proto__; different behaviours in different environments: "__proto__" in {}; // ? "__proto__" in Object.create(null); // ?

Slide 29

Slide 29 text

Never Modify the arguments Object function strict(x) { "use strict"; arguments[0] = "modified"; return x === arguments[0]; } function nonstrict(x) { arguments[0] = "modified"; return x === arguments[0]; } strict("unmodified"); // false nonstrict("unmodified"); // true // instead, make a shallow copy with Array.prototype.slice var args = [].slice.call(arguments); // bonus: `args` is a *real* array typeof args.pop; // "function" typeof arguments.pop; // "undefined"

Slide 30

Slide 30 text

Store Methods on Prototypes function User(name, passwordHash) { this.name = name; this.toString = function() { return "[User " + this.name + "]"; }; this.checkPassword = function(password) { return hash(password) === passwordHash; }; } var u0 = new User(/* ... */); var u1 = new User(/* ... */); var u2 = new User(/* ... */);

Slide 31

Slide 31 text

Store Methods on Prototypes function User(name, passwordHash) { this.name = name; this.checkPassword = function(password) { return hash(password) === passwordHash; }; } User.prototype.toString = function() { return "[User " + this.name + "]"; }; var u0 = new User(/* ... */); var u1 = new User(/* ... */); var u2 = new User(/* ... */);

Slide 32

Slide 32 text

Recognize the Implicit Binding of this function CSVReader(separators) { this.separators = separators || [","]; this.regexp = new RegExp(this.separators.map(function(sep) { return "\\" + sep[0]; }).join("|")); } CSVReader.prototype.read = function(str) { var lines = str.trim().split(/\n/); return lines.map(function(line) { return line.split(this.regexp); // wrong this! }); };

Slide 33

Slide 33 text

Recognize the Implicit Binding of this function CSVReader(separators) { this.separators = separators || [","]; this.regexp = new RegExp(this.separators.map(function(sep) { return "\\" + sep[0]; }).join("|")); } CSVReader.prototype.read = function(str) { var lines = str.trim().split(/\n/); return lines.map(function(line) { return line.split(this.regexp); }, this); // forward outer this-binding to callback };

Slide 34

Slide 34 text

Recognize the Implicit Binding of this function CSVReader(separators) { this.separators = separators || [","]; this.regexp = new RegExp(this.separators.map(function(sep) { return "\\" + sep[0]; }).join("|")); } CSVReader.prototype.read = function(str) { var lines = str.trim().split(/\n/); var self = this; // save a reference to outer this-binding return lines.map(function(line) { return line.split(self.regexp); // use outer this }); };

Slide 35

Slide 35 text

Recognize the Implicit Binding of this function CSVReader(separators) { this.separators = separators || [","]; this.regexp = new RegExp(this.separators.map(function(sep) { return "\\" + sep[0]; }).join("|")); } CSVReader.prototype.read = function(str) { var lines = str.trim().split(/\n/); return lines.map(function(line) { return line.split(this.regexp); }.bind(this)); // bind to outer this-binding };

Slide 36

Slide 36 text

Do Not Call Superclass Constructors Outside Subclass Constructors function Actor(scene, x, y) { this.scene = scene; this.x = x; this.y = y; scene.register(this); } function SpaceShip(scene, x, y) { Actor.call(this, scene, x, y); this.points = 0; } // In order for SpaceShip to be a proper subclass of Actor, // its prototype must inherit from Actor.prototype. SpaceShip.prototype = new Actor(/* ?? */); SpaceShip.prototype = Object.create(Actor.prototype);

Slide 37

Slide 37 text

Do Not Call Superclass Constructors Outside Subclass Constructors function Actor(scene, x, y) { this.scene = scene; this.x = x; this.y = y; scene.register(this); } function SpaceShip(scene, x, y) { Actor.call(this, scene, x, y); this.points = 0; } // ES3 environments don't have `Object.create`, so we'll have to fake it SpaceShip.prototype = (function(ctor, superclass){ return new (ctor.prototype = superclass.prototype, ctor); }(function(){}, Actor));

Slide 38

Slide 38 text

Avoid Inheriting from Standard Classes function Dir(path, entries) { this.path = path; for (var i = 0, n = entries.length; i < n; i++) { this[i] = entries[i]; } } // attempt to subclass Array Dir.prototype = Object.create(Array.prototype); var dir = new Dir("/tmp/mysite", ["index.html", "script.js"]); // Unfortunately, this approach breaks the `length` and // internal `[[Class]]` properties of arrays: dir.length; // 0 {}.toString.call(dir); // "[object Object]" {}.toString.call([]); // "[object Array]"

Slide 39

Slide 39 text

Use null Prototypes to Prevent Pollution var o; function C() { } C.prototype = null; o = new C; Object.getPrototypeOf(o) === null; // false Object.getPrototypeOf(o) === Object.prototype; // true o = Object.create(null); Object.getPrototypeOf(o) === null ; // true o instanceof Object; // false

Slide 40

Slide 40 text

Use hasOwnProperty to Protect Against Prototype Pollution var dict = {}; "alice" in dict; // membership test (including prototype) dict.alice; // retrieval dict.alice = 24; // update delete dict.alice; // deletion "alice" in dict; // false "toString" in dict; // true dict.hasOwnProperty("alice"); // false dict.hasOwnProperty("toString"); // false dict.hasOwnProperty = 10; dict.hasOwnProperty("alice"); // ERROR // safe own-property membership test {}.hasOwnProperty.call(dict, "alice"); // false {}.hasOwnProperty.call(dict, "toString"); // false

Slide 41

Slide 41 text

Use hasOwnProperty to Protect Against Prototype Pollution function Map() { var data = {}; function keyName(userKey) { return '$' + userKey; // protect against setting `data.__proto__` } this.set = function(key, value) { data[keyName(key)] = value; return this; }; this.get = function(key) { return this.has(key) ? data[keyName(key)] : null; }; this.has = (function(){ var hop = {}.hasOwnProperty; return function(key) { return hop.call(data, keyName(key)); }; }()); }

Slide 42

Slide 42 text

Do Not Rely On for-in Enumeration Order function report(highScores) { var lines = []; for (var i in highScores) { // undefined enumeration order var score = highScores[i]; lines.push((+i + 1) + ". " + score.name + ": " + score.points); } return lines.join("\n"); } report([ { name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 } ]);

Slide 43

Slide 43 text

Do Not Rely On for-in Enumeration Order function report(highScores) { var lines = []; for (var i = 0, n = highScores.length; i < n; ++i) { var score = highScores[i]; lines.push((i + 1) + ". " + score.name + ": " + score.points); } return lines.join("\n"); } report([ { name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 } ]);

Slide 44

Slide 44 text

Do Not Rely On for-in Enumeration Order var scores = [98, 74, 85, 77, 93, 100, 89], mean, total; total = 0; for (var score in scores) { total += score; } mean = total / scores.length; mean; // 17636.571428571428 total; // "00123456" total = 0; for (var i = 0 , n = scores.length; i < n; ++i) { total += scores[i]; } mean = total / scores.length; // 88

Slide 45

Slide 45 text

Never Add Enumerable Properties to Object.prototype Object.prototype.allKeys = function () { var result = []; for (var key in this) { result.push(key); } return result; }; // Sadly, this method pollutes even its own result: ({ a: 1, b: 2, c: 3 }).allKeys(); // ["allKeys", "a", "b", "c"]

Slide 46

Slide 46 text

Never Add Enumerable Properties to Object.prototype // avoid adding properties to Object.prototype function allKeys(obj) { var result = []; for (var key in obj) { result.push(key); } return result; } allKeys({ a: 1, b: 2, c: 3 }); // ["a", "b", "c"]

Slide 47

Slide 47 text

Never Add Enumerable Properties to Object.prototype // if you absolutely must add it to Object.prototype, use defineProperty Object.defineProperty(Object.prototype, "allKeys", { value: function () { var result = []; for (var key in this) { result.push(key); } return result; }, writable: true, enumerable: false, // non-enumerable configurable: true }); ({ a: 1, b: 2, c: 3 }).allKeys(); // ["a", "b", "c"]

Slide 48

Slide 48 text

Prefer Iteration Methods to Loops // off-by-one errors suck for (var i = 0; i <= n; ++i) { ... } // extra end iteration for (var i = 1; i < n; ++i) { ... } // missing first iteration for (var i = n; i >= 0; --i) { ... } // extra start iteration for (var i = n - 1; i > 0; --i) { ... } // missing last iteration // replace loops with iteration methods for (var i = 0, n = players.length; i < n; i++) { ++players[i].score; } players.forEach(function(player) { ++player.score; }); // iteration methods also abstract common patterns var trimmed = []; for (var i = 0, n = input.length; i < n; i++) { trimmed.push(input[i].trim()); } var trimmed = input.map(function(s) { return s.trim(); });

Slide 49

Slide 49 text

Reuse Generic Array Methods on Array- Like Objects // array-like: uint32 `length` property, length > largest numeric property function highlight() { [].forEach.call(arguments, function(widget) { widget.setBackground("yellow"); }); } var arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 }; var result = [].map.call(arrayLike, function(s) { return s.toUpperCase(); }); // ["A", "B", "C"] // strings are array-like, too var result = [].map.call("abc", function(s) { return s.toUpperCase(); }); // ["A", "B", "C"]

Slide 50

Slide 50 text

Reuse Generic Array Methods on Array- Like Objects // beware Array.prototype.concat; it is not generic function namesColumn() { return ["Names"].concat(arguments); } namesColumn("Alice", "Bob", "Chris"); // ["Names", { 0: "Alice", 1: "Bob", 2: "Chris" }] // shallow copy `arguments` so we have a real array function namesColumn() { return ["Names"].concat([].slice.call(arguments)); } // ["Names", "Alice", "Bob", "Chris"]

Slide 51

Slide 51 text

Prefer Array Literals to the Array Constructor new Array(2, 3, 4); // [2, 3, 4] new Array(2, 3); // [2, 3] new Array(2); // no array initialiser representation new Array(2) instanceof Array; // true new Array(2).length; // 2 dense = [void 0, void 0]; sparse = new Array(2); dense.length === sparse.length; // true dense[0] === sparse[0]; // true dense[1] === sparse[1]; // true "0" in dense; // true "0" in sparse; // false dense.reduce(function(count) { return count + 1; }, 0); // 2 sparse.reduce(function(count) { return count + 1; }, 0); // 0

Slide 52

Slide 52 text

Accept Options Objects for Keyword Arguments new Alert( 100, 75, 300, 200, "Error", message, "blue", "white", "black", "error", true ); // what is this I don't even new Alert({ x: 100, y: 75, width: 300, height: 200, title: "Error", message: message, titleColor: "blue", bgColor: "white", textColor: "black", icon: "error", modal: true });

Slide 53

Slide 53 text

Accept Options Objects for Keyword Arguments // pull out required options as formal parameters if there's only a few function Alert(parent, message, opts) { this.message = message; opts = opts || {}; // default to an empty options object this.width = opts.width == null ? 320 : opts.width; this.height = opts.height == null ? 240 : opts.height; this.x = opts.x == null ? (parent.width / 2) - (this.width / 2) : opts.x; this.y = opts.y == null ? (parent.height / 2) - (this.height / 2) : opts.y; this.title = opts.title || "Alert"; this.titleColor = opts.titleColor || "gray"; this.bgColor = opts.bgColor || "white"; this.textColor = opts.textColor || "black"; this.icon = opts.icon || "info"; this.modal = !!opts.modal; }

Slide 54

Slide 54 text

Accept Options Objects for Keyword Arguments // an `extend` helper has cleaned this up function Alert(parent, message, opts) { opts = extend({ width: 320, height: 240 }, opts); opts = extend({ x: (parent.width / 2) - (opts.width / 2), y: (parent.height / 2) - (opts.height / 2), title: "Alert", titleColor: "gray", bgColor: "white", textColor: "black", icon: "info", modal: false }, opts); extend(this, opts); }

Slide 55

Slide 55 text

Do Not Block the Event Queue on I/O var text = downloadSync("http://example.com/file.txt"); // blocked console.log(text); downloadAsync("http://example.com/file.txt", function(text) { // continues here when I/O has completed console.log(text); }); // queues I/O and arrives here immediately console.log("guaranteed to execute before the above continuation");

Slide 56

Slide 56 text

Resources ● Herman, David. Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript; Addison-Wesley Professional; 1st Edition, 2012. ● Soshnikov, Dmitry. JavaScript. The Core.;