On Github SerpentJoe / js-preso
Peter Jones for Big Fish Games
// Greeter object var greeter = { name : 'world', greet : function () { alert('Hello ' + this.name + '!'); } }; // Passing `this` syntactically greeter.greet(); // 'Hello world!'; // Forgetting to pass `this` var greetFxn = greeter.greet; greetFxn(); // 'Hello undefined!' (or worse in strict mode) // Passing `this` explicitly greet.call({ name : 'my ragtime gal' }); // 'Hello my ragtime gal!'
var name = 'myself'; // Original values var names = ['alvin', 'simon', 'theodore']; for (var i=0, len=names.length; i<len; i++) { var name = names[i]; // Write to scoped variable in a loop console.log(name + ' was here'); } // value from loop has leaked out because there's no block scoping alert(name); // 'theodore'
Speaking of which …
That means the following gets evaluated pretty ridiculously:
var myValue = 0; function incrementMyValue() { myValue++; return; var myValue; // After the return?!?! } incrementMyValue(); alert(myValue);Try it!
Number.prototype.introduceSelf = function () { alert('Hi there, nice to meet you, I\'m the number ' + this); }; (57).introduceSelf(); // This is like code the guy from Saw would writeTry it!
It gets weirder …
var myString = 'weirdness ahoy'; alert(typeof myString); // 'string' String.prototype.alertType = function () { alert(typeof this); }; myString.alertType(); // 'object'
What the heck is going on???
var person = 'obama'; person.age = 52; // Autoboxed just in time for this to succeed
alert(person.age); // 'undefined'
You can also create boxed types manually.
var myValue = 7; var myBoxedValue = new Number(7); typeof myBoxedValue; // 'object'
But there is no reason to do this, and you shouldn't.
myBoxedValue == 7; // true myBoxedValue === 7; // false myBoxedValue == new Number(7); // false
If you never do this, then you'll never need valueOf
myBoxedValue.valueOf() === 7; // true
Recap: don't use
If eval is invoked directly, the script is executed in the current scope.
var myVar = 'outer'; (function () { var myVar = 'inner'; alert(eval('myVar')); // 'inner' }());
If invocation is indirect, the global scope is applied instead.
var myVar = 'outer'; (function () { var myVar = 'inner'; var myReferenceToEval = eval; // this makes it indirect! alert(myReferenceToEval('myVar')); // 'outer' }());
There's much more to it. Check out the excellent Perfection Kills post on the subject.
OK, so maybe eval really is evil.
null indicates a known unknown: we know something can go here, but we don't have a value for it.
undefined indicates an unknown unknown: we didn't even know something was supposed to be here.
Never set anything to undefined.
Infinity is a value in JS.
1/0 === Infinity; // true
You can make it negative.
var negZero = 1 / (-Infinity); // -0 negZero === 0; // true 1 / negZero; // -Infinity
You can do math with it.
Math.log(Infinity); // Infinity 1/0; // Infinity 1/Infinity; // 0
But you can't serialize it.
JSON.stringify([Infinity]); // '[null]'
So you might want to use it sparingly.
NaN is not a number.
parseInt('what type of number am I'); // NaN
Except it is.
typeof NaN; // 'number'
It doesn't "do" comparisons.
NaN == NaN; // false NaN < Infinity; // false
So you need this handy builtin function to test for it:
isNaN(NaN); // true
But be careful:
isNaN('i mean … i am, technically, not a number'); // true
You must say what you mean:
function isExactlyNaN(value) { return (typeof value === 'number') && isNaN(value); } isExactlyNaN(NaN); // true isExactlyNaN('just a string'); // false
But you should rarely need to be this precise.
This one's easy. Here are all the falsy values:
If you're not on this list, then (!!you) === true.
(function () { 'use strict'; alert('I am in strict mode'); }());
Basically no-BS mode - we should move to this increasingly
Prevents implicit globals
var listsOfNames = [ ['beyonce', 'madonna', 'cher'], ['goku', 'vegeta', 'krillin'], ['romney', 'dole', 'kerry'] ]; for (var i=0, len=listsOfNames.length; i<len; i++) { printAllNames(listsOfNames[i]); } function printAllNames(listOfNames) { // 'use strict'; // We're about to forget to declare `var i` // Would have been an error in strict mode for (i=0, len=listOfNames.length; i<len; i++) { console.log(listOfNames[i]); } }
Another implicit global example:
// Note: be careful to make no changes to the passed object! function addFieldsToIllustrationDescriptor(myIllustrationDescriptor) { // Clone the original to prevent modifications mylIlustrationDescriptor = _.clone(myIllustrationDescriptor, true); // Make changes myIllustrationDescriptor.hello = 'world'; myIllustrationDescriptor.goodnight = 'moon'; // Return modified copy return myIllustrationDescriptor; }
Did you catch it?
It does more too:
var allEls = document.querySelector('*'); allEls.forEach; // undefined
// Use empty array's forEach method, but apply it to allEls [].forEach.call(allEls, function (el) { console.log(el); });
function doAThing(thing1, thing2/*, more things ...*/) { // arguments doesn't have a slice method, so this fails var moreThings = arguments.slice(2); }
function doAThing(thing1, thing2/*, more things ...*/) { // We borrowed the method var moreThings = [].slice.call(arguments, 2); }
MyClass.prototype.getAndBroadcast = function (resourceName) { this.fetchResource(resourceName, function (data) { this.triggerEvent('getResource', data); // Above fails because `this` has wrong value! }); };
MyClass.prototype.getAndBroadcast = function (resourceName) { this.fetchResource(resourceName, function (data) { this.triggerEvent('getResource', data); // Everything's peachy now }.bind(this)); };
Function.prototype.bind is available natively in IE8 and up.
But it's also easy to implement yourself.
Function.prototype.bind = function (ctx) { var myself = this; return function (/*arguments*/) { return myself.apply(ctx, arguments); }; };
Problem: users of your classical OO implementation want to use super in their methods.
Solution: wrap their methods!
// save old method var originalMyMethod = MyClass.prototype.myMethod; // wrap and overwrite MyClass.prototype.myMethod = function () { // Set this._super so the user's method can use it this._super = MyParentClass.prototype.myMethod; // Invoke the original var result = originalMyMethod.apply(this, arguments); // Unset this._super this._super = null; // Done return result; };
Be careful: a wrapped function is a bound function.
When you control invocation of a function, you'd better get it right!
Get used to writing .apply(this, arguments)
What's wrong with switch?
Instead of this:
var myValue; switch (type) { case 'absolute': myValue = parseInt(value); break; case 'random': myValue = Math.random(); break; default: console.log('WARNING: unknown type'); myValue = null; break; }
Consider this:
// Define strategies var STRATEGIES = { absolute : function (value) { return parseInt(value); }, random : function (value) { return Math.random(); } }; function DEFAULT_STRATEGY(value) { console.log('WARNING: unknown type'); return null; } // Choose strategy var strategy = STRATEGIES[type] || DEFAULT_STRATEGY; // Execute strategy myValue = strategy.call(null, value);
Advantages:
What does this snippet do?
var myIds = []; for (var i=0, len=nodes.length; i<len; i++) { if (!(nodes[i] instanceof MyClass)) { continue; } myIds.push(nodes[i].id); }
How about this one?
var myIds = nodes .filter(function (node) { return (node instanceof MyClass); }) .map(function (node) { return node.id; });
Underscore is a library that provides an extended API for chainable functional programming
_(document.querySelectorAll('script[src]')) // Get the src attribute .pluck('src') // Ensure fully qualified URLs .map(function (url) { var linkEl = document.createElement('a'); linkEl.href = url; return url; }) // make all caps for some reason .invoke('toUpperCase') // And on and on …
Lo-Dash is a fork of Underscore that improves performance and extends the API.
Which code would you rather come across?
doNetworkRequest('casino', 'default', 'myTestAlert', 480, true);
doNetworkRequest({ gameType : 'casino', viewSet : 'default', viewName : 'myTestAlert', size : 480, retina : true });
// Even in the minimal case, it needs a callback to run upon completion doNetworkRequest(callbackFxn);
var requesting = doNetworkRequest(); // Specify one handler immediately requesting.done(function (data) { console.log('Look at all this data!'); console.log(data); }); // Wait a while and then specify another var ONE_HOUR = 60 * 60 * 1000; // millis setTimeout(function () { requesting.done(function (data) { console.log('Guys, remember when we got this sweet data?'); console.log(data); }); }, ONE_HOUR);
function ImageAsset(url) { this._url = url; } ImageAsset.prototype.fetch = function () { var url = this._url; return new Promise(function (resolve, reject) { var img = new Image; img.onload = function () { resolve(img); }; img.onerror = function () { reject(img); }; img.src = url; }); };
function ImageAsset(url) { this._url = url; this.fetch(); // Start fetching now so we won't have to wait later } ImageAsset.prototype.fetch = function () { // Same implementation as before };
Problem: the result of that fetch disappears!
ImageAsset.prototype.fetch = function () { if (!this._fetching) { var url = this._url; this._fetching = new Promise(function (resolve, reject) { // Same implementation as before }); } return this._fetching; };
Now the first promise to fetch the texture gets saved, and subsequent requests receive the very same promise.
That means the actual work (the network request) is guaranteed to happen exactly once, no matter how many times it's requested.
We assume it has a method play which returns a promise
// subclass ImageAsset function Animation(url) { ImageAsset.apply(this, arguments); // call super } Animation.prototype = Object.create(ImageAsset.prototype); // define play method Animation.prototype.play = function () { … // Return a promise that resolves when the animation completes };
Calling code is getting race conditions because it's trying to play an animation that hasn't been fetched.
No problem!
Animation.prototype.play = function () { return this.fetch() .then(function (texture) { … // Original logic goes here }); };
Calling fetch is always safe because it's idempotent.
You can do that. You can go crazy.
var anim = new Animation(); anim.fetch() // play .play() // post to Facebook .then(function () { return new Promise(function (resolve, reject) { FB.ui({ … }, resolve); }); }) // Redirect and show a reward for sharing .then(function () { location.pathname = '/one/million/chips'; })
var anim = new Animation(); anim.fetch(function () { // When done fetching, play this.play(function () { // When done playing, post to Facebook FB.ui({ … }, function () { // When done posting, show a reward for sharing location.pathname = '/one/million/chips'; }); }); });
What's wrong with this source file?
// alertHelloWorld.js var myMessage = 'hello world'; setTimeout(function () { alert(myMessage); }, 1e3);
Nothing, unless this file loads on the same page:
// alertBeAManHulk.js var myMessage = 'be a man, hulk'; setTimeout(function () { alert(myMessage); }, 1e3);
myMessage was meant as private in both cases, but they overwrote each other!
Operating in top-level scope means all our implementation details are exposed!
How can we control what we expose?
// alertHelloWorld.js (function () { var myMessage = 'hello world'; setTimeout(function () { alert(myMessage); }, 1e3); }());
// alertBeAManHulk.js (function () { var myMessage = 'be a man, hulk'; setTimeout(function () { alert(myMessage); }, 1e3); }());
Now they don't interfere!
Drawback: if I want to expose something to other modules, I need to have knowledge of where to put it.
// Logger.js (function () { var Logger = { log : function (msg) { console.log(msg); } }; // export window.Logger = Logger; }());
Now what if I want to put it somewhere else?
// Logger.js (function () { // … impl … // export window.sag.Logger = Logger; // At least until it needs to move again }());
Asynchronous Module Definition makes use of two top level functions, define and require.
Define a module:
// util/Logger.js define([], function () { return { log : function (msg) { console.log(msg); } }; });
Require a module:
// Logger.js require(['util/Logger'], function (Logger) { Logger.log('this is so much better than using console.log directly'); });
<script src="main.js"></script>and main.js will require games/casino.js , which will require minigames/plinko.js , which will require util/Logger.js.
Drawback: the syntax is bizarre.
Corollary: long lists of dependencies are hideous.
// Break it onto two lines require(['look', 'at', 'how', 'many', 'modules', 'i', 'need'], function (look, at, how, many, modules, i, need) { // Actual work goes here });
Imagine having to fix a merge conflict in that list!
What I really want to write is more like:
var look = require('look'); var at = require('at'); var how = require('how'); var many = require('many'); var modules = require('modules'); var i = require('i'); var need = require('need');
That will look way better in a diff, and it makes more sense too.