Taking Your Javascript Further
Gregory Cornelius – @gcorne
Code Wrangler, Automattic
Boston WordPress Meetup July 28, 2014
Agenda
- General Strategies for Thinking and Learning
- Some Advanced JavaScript Fundamentals
Challenge Yourself to Go Deeper!
- Do I know how the code really works?
- Can the code I'm working on be written more simply? Where is the complexity?
- Can I make better use of the libraries I am using?
- Are the multiple places in the code where the same idioms are being repeated?
- Are there edge cases that I might be missing?
- Am I guessing?
Make Time For Learning
Hit pause every so often.
Read, think, and experiment.
Learning Strategies
- Read books written by experts.
- Read documentation and specs. Try and take your time understand them and then go to other sources to explain them.
- Read code (ultimate source of truth).
- 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