Taking Your Javascript Further

Gregory Cornelius – @gcorne

Code Wrangler, Automattic

Boston WordPress Meetup July 28, 2014

Agenda

  1. General Strategies for Thinking and Learning
  2. Some Advanced JavaScript Fundamentals

Challenge Yourself to Go Deeper!

  1. Do I know how the code really works?
  2. Can the code I'm working on be written more simply? Where is the complexity?
  3. Can I make better use of the libraries I am using?
  4. Are the multiple places in the code where the same idioms are being repeated?
  5. Are there edge cases that I might be missing?
  6. Am I guessing?

Make Time For Learning

Hit pause every so often.
Read, think, and experiment.

Learning Strategies

  1. Read books written by experts.
  2. Read documentation and specs. Try and take your time understand them and then go to other sources to explain them.
  3. Read code (ultimate source of truth).
  4. Experiment, play, have fun.

Contribute to WordPress Core!

Advanced JavaScript Fundamentals

Function Definitions


function makeMoney( dollars ) {
	var money = '';
	for ( var i = 0; i < dollars; i++ ) {
		money += '$';
	}
	return money;
};

var makeMoneyTwo = function( dollars ) {
	var money = '';
	for ( var i = 0; i < dollars; i++ ) {
		money += '$';
	}
	return money;
};

var makeMoneyThree = makeMoney;
console.log( makeMoney( 5 ) );
console.log( makeMoneyTwo( 5 ) );
console.log( makeMoneyThree( 5 ) );
				
run

Closures


var magicMoneyMachine = function() {
	var money = '';

	function makeSome( dollars ) {
		if ( dollars > 40 ) {
			throw new Error( "Don't be greedy!" );
		}
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}
		return money;
	}

	return makeSome;
};

var makeMoney = magicMoneyMachine();

console.log( makeMoney( 5 ).length );
console.log( makeMoney( 15 ).length );
console.log( makeMoney( 50 ).length );
				
run

Immediately Invoked Functions (IIFE)


(function() {

	var money = '';

	function makeMoney( dollars ) {
		if ( dollars > 40 ) {
			throw new Error( "Don't be greedy!" );
		}
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}
		return money;
	}

	console.log( makeMoney( 10 ) );
	console.log( makeMoney( 20 ) );

})();

console.log( makeMoney( 25 ) );

				
run

IIFE — WordPress Core


(function($){
	var identifier = 0,
		zindex = 9999;


		$.widget('wp.pointer', {
			options: {
				pointerClass: 'wp-pointer',
				pointerWidth: 320,

			// a bunch of code

		});

})(jQuery);

				

src: wp-includes/js/wp-pointer.js

Object Literal


var magicBank = {
	money: '',
	moreMoney: function( dollars ) {
		for ( var i = 0; i < dollars; i++ ) {
			this.money += '$';
		}
		return this.money;
	}
};

console.log( magicBank.moreMoney( 20 ).length );
console.log( magicBank.moreMoney( 100 ).length );

				
run

Object as Namespaces


window.wp = window.wp || {};
(function ($) {
wp.template = _.memoize(function ( id ) {
	var compiled,
		options = {
			evaluate:    /<#([\s\S]+?)#>/g,
			interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
			escape:      /\{\{([^\}]+?)\}\}(?!\})/g,
			variable:    'data'
		};

	return function ( data ) {
		compiled = compiled ||
			_.template(
				$( '#tmpl-' + id ).html(),
				null,
				options
			);
		return compiled( data );
	};
});

// ...

})( jQuery );
			

src: wp-includes/js/wp-util.js

Module Pattern


function makeMagicBank() {
	var money = '';
	function moreMoney( dollars ) {
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}
		return money;
	}

	function donate( dollars ) {
		moreMoney( dollars );
		return 'thanks!';
	}

	return {
		moreMoney: moreMoney,
		donate: donate
	};
};

var magicBank = makeMagicBank();
console.log( magicBank.donate( 5 ) );
console.log( magicBank.moreMoney( 10 ) );
console.log( magicBank.moreMoney( 20 ) );
			
run

Module Pattern — WordPress Core


(function( $, window, undefined ) {
	var Heartbeat = function() {
		var $document = $(document),
			settings = {};

		function initialize() {}
		function enqueue() {}
		function isQueued() {}
		function dequeue() {}
		// etc...

		return {
			enqueue: enqueue,
			dequeue: dequeue,
			isQueued: isQueued
			// etc...
		};
	}

	// Ensure the global `wp` object exists.
	window.wp = window.wp || {};
	window.wp.heartbeat = new Heartbeat();
}( jQuery, window ));
				

src: wp-includes/js/heartbeat.js

Object Constructor Function


var MagicBank = function() {

	this.money = '';
	this.moreMoney = function( dollars ) {
		for ( var i = 0; i < dollars; i++ ) {
			this.money += '$';
		}
		return this.money;
	}
};

var magicBank = new MagicBank();
console.log( magicBank.moreMoney( 25 ) );
console.log( magicBank.moreMoney( 25 ) );
				
run

Prototypal Inheritance


var MagicBank = function() {
	this.money = '';
};
MagicBank.prototype.moreMoney = function( dollars ) {
	for ( var i = 0; i < dollars; i++ ) {
		this.money += '$';
	}
	return this.money;
};

var magicBank = new MagicBank();
console.log( magicBank.moreMoney( 25 ) );

var AutoBank = function() {
	// call parent constructor
	MagicBank.call( this );
	// override property initialization
	this.money = '$$$$$';
}
AutoBank.prototype = Object.create( MagicBank.prototype );
AutoBank.prototype.constructor = AutoBank;

bank = new AutoBank();
console.log( bank.moreMoney( 25 ) );
				
run

Prototype Chain

Dual-Use Function Body


var MagicBank = function() {
	// return new object instance
	if ( ! ( this instanceof MagicBank ) ) {
		return new MagicBank();
	}

	// set up defaults
	this.money = '';
};

MagicBank.prototype.moreMoney = function( dollars ) {
	for ( var i = 0; i < dollars; i++ ) {
		this.money += '$';
	}
	return this.money;
};

bank = MagicBank();
console.log( bank.moreMoney( 25 ) );
				
run

Composing Objects


function List( node, itemTemplate, items ) {
	this.items = items;
	this.el = node;
	this.template = itemTemplate
};

List.prototype.render = function() {
	var html = '<ol>';
	this.items.forEach( function( item ) {
		html += this.template( item );
	}, this );
	html += '</ol>';
	this.el.innerHTML = html;
};

function Sort( prototype, prop ) {

	prototype.sort = function() {
		this[ prop ].sort()
	}
}

Sort( List.prototype, 'items' );

			

Composing Objects (usage )


function template ( item ) {
	return '<li>' + item + '</li>';
}
var list = new List(
	document.getElementById('list'),
	template,
	[ 'pear', 'orange', 'apple', 'banana' ]
);

list.sort();
list.render();
			
run

results:

Inheritance in Backbone


var Bank = Backbone.Model.extend({
	defaults: {
		money: ''
	},
	makeMoney: function( dollars ) {
		var money = this.get( 'money' );
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}
		this.set( 'money', money );
		return money;
	}
});

var magicBank = new Bank();
console.log( magicBank.makeMoney( 20 ) );

				
run

Composition in Backbone

var Bank = Backbone.Model.extend({
	defaults: {
		money: ''
	},
	makeMoney: function( dollars ) {
		var money = this.get( 'money' );
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}
		this.set( 'money', money );
		return money;
	}
});

var BankView = Backbone.View.extend({
	render: function() {
		this.$el.text( this.model.get( 'money' ) );
	}
});

var magicBank = new Bank();
var view = new BankView({
	el: document.getElementById( 'bank-view' ),
	model: magicBank
});

magicBank.makeMoney( 20 );
view.render();
run

An Aside on "this"

var Bank = Backbone.Model.extend({
	defaults: {
		money: ''
	},
	makeMoney: function( dollars ) {
		var money = this.get( 'money' );
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}

		this.set( 'money', money );

		function log() {
			console.log( this.get( 'money' ) );
		}

		log();
		return money;
	}
});

magicBank = new Bank();

magicBank.makeMoney( 20 );

				
run

An Aside on "this" (part 2)

var Bank = Backbone.Model.extend({
	defaults: {
		money: ''
	},
	makeMoney: function( dollars ) {
		var money = this.get( 'money' );
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}

		this.set( 'money', money );

		function log() {
			console.log( this.get( 'money' ) );
		}

		log = log.bind( this );
		log();
		return money;
	}
});

magicBank = new Bank();

magicBank.makeMoney( 20 );

				
run

Messaging Between Components


var MagicBank = function() {
	this.money = '';
	this.makeMoney = function( dollars ) {
		for ( var i = 0; i < dollars; i++ ) {
			this.money += '$';
		}
		var event = new CustomEvent( 'moneyAdded', { detail: 
			{ money: this.money }
		} );
		document.dispatchEvent( event );

		return this.money;
	}
};

var listener = function( event ) {
	console.log( 'added', event.detail.money );
};

// Listen for the event.
document.addEventListener( 'moneyAdded', listener );
var bank = new MagicBank();
bank.makeMoney( 20 );
bank.makeMoney( 40 );

// Listen for the event.
document.removeEventListener( 'moneyAdded', listener );
				
run

Messaging in Backbone


var MagicBank = Backbone.Model.extend({
	defaults: {
		money: ''
	},
	makeMoney: function( dollars ) {
		var money = this.get( 'money' );
		for ( var i = 0; i < dollars; i++ ) {
			money += '$';
		}

		this.set( 'money', money );
		return money;
	}
});

var bank = new MagicBank();
bank.on( 'change:money', function( model, money ) {
	console.log( 'updated:', money );
} );
bank.makeMoney( 20 );
bank.makeMoney( 40 );

bank.off( 'change:money' );
				
run

Web Timing APIs


var timing = window.performance.timing;

console.log( timing );

console.log( timing.responseEnd - timing.navigationStart + "ms response time" );
console.log( timing.domContentLoadedEventStart - timing.navigationStart + "ms jQuery dom ready" );
console.log( new Date().getTime() - timing.navigationStart + "ms since document loaded" );
				
run

Further Areas for Exploration

  1. node
  2. requestAnimationFrame
  3. ECMAScript 6 (harmony)
  4. WebGL
  5. Canvas
  6. Web Workers
  7. Emscripten & asm.js
  8. Web Sockets

Go Further!