Rick Waldron

@rwaldron

github.com/rwldrn

[ Ecma/TC39, jQuery Core, Johnny-Five ]





EcmaScript 6

My Favorite Things






gul.ly/dcx





Ecma, TC39

Mozilla, Yahoo, Microsoft, Google, Intel & jQuery

(Adobe, Opera, Dojo & others)





EcmaScript 1, 1997

Based on Netscape's JavaScript





EcmaScript 2, 1998

Editorial for ISO/IEC 16262





EcmaScript 3, 1999

Regular Expressions, String methods, try/catch, function expressions





EcmaScript 4 (Abandoned)





EcmaScript 5/5.1, 2009/2011

Object, Function & Array extensions, strict mode





EcmaScript 6

Object APIs, Classes, Modules, Concise Methods, Arrow Functions, Rest and Spread, Property Initializers, Proxy/Reflect, Collections...





Experiment!

Firefox, Chrome

gul.ly/dct





extending & mixins

ES3


function each(obj, iterator, context) {
  if (obj == null) return;
  if (obj.length === +obj.length) {
    for (var i = 0, l = obj.length; i < l; i++) {
      iterator.call(context, obj[i], i, obj);
    }
  } else {
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        iterator.call(context, obj[key], key, obj);
      }
    }
  }
};
function extend(obj) {
  each([].slice.call(arguments, 1), function(source) {
    if (source) {
      for (var prop in source) {
        obj[prop] = source[prop];
      }
    }
  });
  return obj;
};

ES5



function merge( target, source ) {
  return Object.keys( source ).reduce(function( accum, key ) {
    return (accum[ key ] = source[ key ]) && accum;
  }, target );
}

function extend() {
  return [].slice.call( arguments ).reduce(function( target, source ) {
    return merge( target, source );
  });
}


ES6

function extend( ...sources ) {
  return sources.reduce( Object.assign );
}

// Or...

function extend( ...sources ) {
  return sources.reduce( Object.mixin );
}

// Eric F. wrote this during the September meeting:
let defaults  = {},
    overrides = {};

function foo(bar, options) {
    // YUI
    options = Y.merge(defaults, options, overrides);

    // Underscore
    options = _.extend({}, defaults, options, overrides);

    // ES6
    options = [defaults, options, overrides].reduce(Object.assign, {});
}

// ES6 YUI `Object.assign()` sugar.
Y.assign = function (target, ...sources) {
    return sources.reduce(Object.assign, target);
};

// ES6 back-compat `Y.merge()`.
Y.merge = function (...sources) {
    return Y.assign({}, ...sources);
}

Parts List


// Rest Params
function foo(...rest) {}

// The crossroads of extend, merge, mixin, etc.
// Simple batch [[Put]] assignment (not [[DefineOwnProperty]])
Object.assign( target, source ); // => modified target


// Complex descriptor copying, rebind super, all [[DefineOwnProperty]]
Object.mixin( target, source ); // => modified target


Object.assign implementation, proposal, accepted design

Object.mixin implementation





jQuery

function jQuery( selector, context ) {
  var k = -1,
      nodes = (context || document).querySelectorAll( selector );

  while ( ++k < nodes.length ) {
    this[ k ] = nodes[ k ];
  }
  this.length = nodes.length;
}
jQuery.prototype = {
  length: 0,
  pushStack: function( elems ) {
    return jQuery.merge( this.constructor(), elems );
  },
  each: function( callback, args ) {
    return jQuery.each( this, callback, args );
  },
  slice: function() {
    return this.pushStack( [].slice.apply( this, arguments ) );
  },
  eq: function( i ) {
    var len = this.length,
      j = +i + ( i < 0 ? len : 0 );
    return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
  },
  first: function() {
    return this.eq( 0 );
  },
  last: function() {
    return this.eq( -1 );
  },
  map: function( callback ) {
    return this.pushStack( jQuery.map(this, function( elem, i ) {
      return callback.call( elem, i, elem );
    }));
  },
  push: [].push,
  sort: [].sort,
  splice: [].splice
};
class jQuery extends Array {
  constructor( arg = "", context = document ) {
    if ( !(this instanceof jQuery) ) {
      return new jQuery( arg, context );
    }
    super();

    this.push(
      ...(arg.nodeType ? [ arg ] : context.querySelectorAll(arg))
    );
  }
  first() {
    return this.slice( 0, 1 );
  }
  last() {
    return this.slice( this.length - 1, this.length )
  }
}

// Automatically inherits forEach, map, reduce, filter,
// push, pop, slice, splice!

gul.ly/dd1

Parts List


// Class, can inherit from Natives!!!
class jQuery extends Array {}

// Parameter Default values
constructor( selector = "", context = document )

// Real super!
super();

// Spread operator (turns the NodeList into an actual array!)
[ ...context.querySelectorAll(selector) ]

// Concise Methods
...
first() {
  return this.slice( 0, 1 );
}
last() {
  return this.slice( this.length - 1, this.length )
}






"private" data

var Account = (function() {
  var accounts = [], ledgers = [];

  function getLedger( acct ) {
    return ledgers[ accounts.indexOf(acct) ];
  }
  function Account( initial ) {
    accounts.push(this);
    ledgers.push({ deposit: [], withdrawal: [] });

    initial = initial !== undefined ? initial : 0;
    this.transaction( "deposit", initial );
  }
  Account.prototype.transaction = function( type, amount ) {
    getLedger(this)[ type ].push({
      date: Date.now(),
      amount: amount
    });
    return this;
  };
  Account.prototype.balance = function() {
    var ledger, total;
    ledger = getLedger(this);
    total = ledger.deposit.reduce(function(v, rec) { return v + rec.amount; }, 0) +
      ledger.withdrawal.reduce(function(v, rec) { return v - rec.amount; }, 0);

    return ( total / 100 ).toFixed(2);
  };
  return Account;
}());

var a = new Account(10000);

console.log( a.transaction("withdrawal", 2500).balance() );

module Client {
  let ledgers = new WeakMap();
  export class Account {
    constructor( initial = 0 ) {
      ledgers.set( this, { deposit: [], withdrawal: [] });

      this.transaction( "deposit", initial );
    }
    transaction( type, amount, date = Date.now() ) {
      ledgers.get(this)[ type ].push({ date, amount });
      return this;
    }
    balance() {
      var ledger, total;
      ledger = ledgers.get(this),
      total = ledger.deposit.reduce(( v, rec ) => v + rec.amount, 0) +
          ledger.withdrawal.reduce(( v, rec ) => v - rec.amount, 0);

      return ( total / 100 ).toFixed(2);
    }
  }
}
import { Account } from Client;

var a = new Account(10000);

console.log( a.transaction("withdrawal", 2500).balance() );

http://gul.ly/dcy

Parts List


// Modules
module Client { this scope is unique to the module definition! }

// Block scope, let.
let books...

// WeakMap, create relationships between arbitrary objects w/o
// creating leaky references!
let books = new WeakMap();

// exported class
export class Account {}

// Property Initialization Shorthand
{ date, amount } => { date: date, amount: amount }

// Arrow Function; implicit tail returns!
book.deposit.reduce(( v, rec ) => v + rec.amount, 0)



Localizing Methods



var parse = JSON.parse,
    stringify = JSON.stringify;

console.log( parse('{ "a": 1 }') );
console.log( stringify({ a: 1 }) );



var { parse, stringify } = JSON;

console.log( parse('{ "a": 1 }') );
console.log( stringify({ a: 1 }) );

// ..............


var { Model } = Backbone;


var Animal = new Model({ .... });


Unique Arrays

function unique( array ) {
  var i, length, ret;
  length = array.length;
  ret = [];

  for ( i = 0; i < length; i++ ) {
    if ( ret.indexOf( array[i] ) === -1 ) {
      ret.push( array[i] );
    }
  }
  return ret;
}

console.log(
  unique([ 1, 1, 2, 3, 4, 4, 4, 5 ])
);
// [ 1, 2, 3, 4, 5 ]

function unique( array ) {
  return [ ...new Set( array ) ];
}

console.log(
  unique([ 1, 1, 2, 3, 4, 4, 4, 5 ])
);
// [ 1, 2, 3, 4, 5 ]

Shorthands: Property Initialization

jQuery.each( [ "get", "post" ], function( i, type ) {
  jQuery[ type ] = function( url, data, success, dataType ) {
    if ( jQuery.isFunction( data ) ) {
      type = type || success;
      success = data;
      dataType = undefined;
    }
    return jQuery.ajax({
      url: url,
      type: type,
      dataType: dataType,
      data: data,
      success: success
    });
  };
});
[ "get", "post" ].forEach(function( type ) {
  jQuery[ type ] = function( url, data, success, dataType ) {
    if ( jQuery.isFunction( data ) ) {
      type = type || success;
      success = data;
      dataType = undefined;
    }

    return jQuery.ajax({ url, type, dataType, data, success });
  };
});

Shorthands: Concise Methods

function request( options ) {
  console.log( options.success(), options.error() );
}
request({
  success: function() {
    return "Success!";
  },
  error: function() {
    return "Error!";
  }
});


function request(options) {
  console.log( options.success(), options.error() );
}
request({
  success() {
    return "Success!";
  },
  error() {
    return "Error!";
  }
});

this

function Video( src ) {
    this.element = document.createElement("video");

    this.element.src = src;

    this.element.addEventListener("click", function( event ) {
      // video element has two buttons: play and pause;
      // event.currentTarget.action will be either of these
      this.element[ event.currentTarget.action ]();

    }, false);
  }

  document.body.appendChild( new Video("foo.webm") );
  

...is not what I expected it to be

this, Solutions?

function Video( src ) {
  var that = this

  this.element = document.createElement("video");

  this.element.src = src;

  this.element.addEventListener("click", function( event ) {
    // video element has two buttons: play and pause;
    // event.currentTarget.action will be either of these
    that.element[ event.currentTarget.action ]();

  }, false);
}

document.body.appendChild( new Video("foo.webm") );
function Video( src ) {
  this.element = document.createElement("video");

  this.element.src = src;

  this.element.addEventListener("click", function( event ) {
    // video element has two buttons: play and pause;
    // event.currentTarget.action will be either of these
    this.element[ event.currentTarget.action ]();

  }.bind(this), false);
}

document.body.appendChild( new Video("foo.webm") );

this, Solution.

function Video( src ) {

  this.element = document.createElement("video");

  this.element.src = src;

  this.element.addEventListener("click", event => {

    this.element[ event.currentTarget.action ]();

  }, false);
}

document.body.appendChild( new Video("foo.webm") );




And now I don't feel so bad.




es-discuss@mozilla.org

https://twitter.com/esdiscuss

<SPACE> will advance to the next slide