Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ask Not What JavaScript Can Do For You

Jon Bretman
September 14, 2013

Ask Not What JavaScript Can Do For You

Exploring how hard it is to write well structured and error safe code in vanilla JavaScript.

Jon Bretman

September 14, 2013
Tweet

More Decks by Jon Bretman

Other Decks in Programming

Transcript

  1. ASK NOT WHAT
    JAVASCRIPT CAN DO
    FOR YOU
    Embracing the language and
    writing sensible code



    @jonbretman
    jonbretman.co.uk

    Mobile Web Developer @ Badoo
    http://techblog.badoo.com
    @badootechbog
    1  

    View full-size slide

  2. var topics = [!
    'Type Checking',!
    'Classes and Inheritance’,!
    'Asynchronous Code',!
    'Performance'!
    ];!
     
    2  

    View full-size slide

  3. topics.shift();!
    "Type Checking"!
     
    3  

    View full-size slide

  4. !
    !
    !
    My Awesome App!
    !
    !
    !
    !
     
    5  

    View full-size slide

  5. Api.get('/conversations', function (conversations) {!
    !
    var intros = conversations.map(function (c) {!
    var name = c.theirName;!
    var mostRecent = c.messages[0].text.substring(0, 30);!
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });!
    6  

    View full-size slide

  6. Api.get('/conversations', function (conversations) {!
    !
    var intros = conversations.map(function (c) {!
    var name = c.theirName;!
    var mostRecent = c.messages[0].text.substring(0, 30);!
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });!
    7  

    View full-size slide

  7. Api.get('/conversations', function (err, conversations) {!
    !
    var intros = conversations.map(function (c) {!
    var name = c.theirName;!
    var mostRecent = c.messages[0].text.substring(0, 30);!
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });!
    A lot of things have to go right here
    8  

    View full-size slide

  8. if (data.value) {!
    // do something with value!
    }!
    !
    !
    TypeError: Cannot read property 'value' of null!
    !
    !
    !
    !
     
     
     
    9  

    View full-size slide

  9. if (data && data.value) {!
    var index = data.value.indexOf('something');!
    // do something with index!
    }!
    !
    !
    !
    TypeError: Object # has no method ‘indexOf’!
    !
    !
    !
    !
    !
     
      10  

    View full-size slide

  10. if (data && data.callback) {!
    var result = data.callback();!
    // do something with result!
    }!
    !
    !
    TypeError: Property 'callback' of object #
    is not a function  
     
    11  

    View full-size slide

  11. typeof {};!
    "object"!
    !
    typeof 'hello';!
    "string"!
    !
    typeof 5;!
    "number”!
    typeof function () {};!
    "function"!
    !
    typeof undefined;!
    "undefined"!
    !
    typeof true;!
    "boolean"!
    !
    12  

    View full-size slide

  12. typeof [];!
    "object"!
    !
    typeof null;!
    "object"!
    !
    typeof new Date();!
    "object"!
    typeof /jsconf/;!
    "object"!
    !
    typeof document.body;!
    "object"!
    !
    typeof NaN;!
    "number"!
    !
    13  

    View full-size slide

  13. Object.prototype.toString()!
     
     
     
     
    14  

    View full-size slide

  14. •  If the this value is undefined, return "[object
    Undefined]”.!
    !
    15  

    View full-size slide

  15. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    16  

    View full-size slide

  16. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    •  Let class be the value of the [[Class]] property of
    this.!
    !
    !
    17  

    View full-size slide

  17. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    •  Let class be the value of the [[Class]] property of
    this.!
    •  Return the String value that is the result of
    concatenating the three Strings "[object ", class,
    and "]".!
    !
    18  

    View full-size slide

  18. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    •  Let class be the value of the [[Class]] property of
    this.!
    •  Return the String value that is the result of
    concatenating the three Strings "[object ", class,
    and "]".!
    !
    Function.prototype.call()!
    ! ! ! ! ! ! !or!
    Function.prototype.apply()!
      19  

    View full-size slide

  19. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    var match = toString.call(o).match(regex);!
    return match[1].toLowerCase();!
    };!
    20  

    View full-size slide

  20. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    var match = toString.call(o).match(regex);!
    return match[1].toLowerCase();!
    };!
    this === o  
    21  

    View full-size slide

  21. type({});!
    "object"!
    !
    type('hello');!
    "string"!
    !
    type(5);!
    "number”!
    type(function () {});!
    "function"!
    !
    type(undefined);!
    "undefined"!
    !
    type(true);!
    "boolean"!
    !
    22  

    View full-size slide

  22. type([]);!
    "array"!
    !
    type(null);!
    "null"!
    !
    type(new Date());!
    "date"!
    type(/jsconf/);!
    "regex"!
    !
    type(document.body);!
    "htmlbodyelement"!
    !
    type(NaN);!
    "number"!
    !
    23  

    View full-size slide

  23. type([]);!
    "array"!
    !
    type(null);!
    "null"!
    !
    type(new Date());!
    "date"!
    type(/jsconf/);!
    "regex"!
    !
    type(document.body);!
    "htmlbodyelement"!
    !
    type(NaN);!
    "number"!
    !
    ???
    24  

    View full-size slide

  24. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    !
    if (o && o.nodeType === 1) {!
    return 'element';!
    }!
    !
    var match = toString.call(o).match(regex);!
    var _type = match[1].toLowerCase();!
    !
    if (_type === 'number' && isNaN(o)) {!
    return 'nan';!
    }!
    !
    return _type;!
    };! 25  

    View full-size slide

  25. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    !
    if (o && o.nodeType === 1) {!
    return 'element';!
    }!
    !
    var match = toString.call(o).match(regex);!
    var _type = match[1].toLowerCase();!
    !
    if (_type === 'number' && isNaN(o)) {!
    return 'nan';!
    }!
    !
    return _type;!
    };! 26  
    Special case for DOM elements

    View full-size slide

  26. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    !
    if (o && o.nodeType === 1) {!
    return 'element';!
    }!
    !
    var match = toString.call(o).match(regex);!
    var _type = match[1].toLowerCase();!
    !
    if (_type === 'number' && isNaN(o)) {!
    return 'nan';!
    }!
    !
    return _type;!
    };! 27  
    Special case for NaN

    View full-size slide

  27. Api.get('/conversations', function (conversations) {!
    !
    if (type(conversations) !== 'array') {!
    App.renderMessages([]);!
    return;!
    }!
    !
    var intros = conversations.map(function (c) {!
    !
    if (type(c) !== 'object') {!
    return '';!
    }!
    !
    var name = type(c.theirName) === 'string' ? c.theirName : '';!
    var mostRecent = '';!
    !
    if (type(c.messages) === 'array' ||!
    type(c.messages[0]) === 'object' ||!
    type(c.messages[0].text) === 'string') {!
    mostRecent = c.messages[0].text.substring(0, 30);!
    }!
    !
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });  
    28  

    View full-size slide

  28. Api.get("/conversations",function(e){var t=e.map(function(e)
    {var t=e.theirName;var
    n=e.messages[0].text.substring(0,30);return t+":
    "+n});App.renderMessages(t)})!
    !
    !
    !
    !
    !
    !
    !
    !
    !
    Api.get("/conversations",function(e){if(type(e)!=="array")
    {App.renderMessages([]);return}var t=e.map(function(e)
    {if(type(e)!=="object"){return""}var
    t=type(e.theirName)==="string"?e.theirName:"";var
    n="";if(type(e.messages)==="array"||
    type(e.messages[0])==="object"||
    type(e.messages[0].text)==="string")
    {n=e.messages[0].text.substring(0,30)}return t+":
    "+n});App.renderMessages(t)})  
    + 137%
    29  

    View full-size slide

  29. Check for types where
    not doing so could
    cause an exception or
    make code ambiguous
    30  

    View full-size slide

  30. topics.shift();!
    "Classes and Inheritance"!
     
    31  

    View full-size slide

  31. public class Person {!
    !!
    !private String name;!
    !
    !public Person (String name) {!
    ! !this.name = name;!
    !}!
    !!
    !public String getName () {!
    ! !return this.name;!
    !}!
    !
    }!
    !
    public class Doctor extends Person {!
    !!
    !public Doctor (String name) {!
    ! !super("Dr." + name);!
    !}!
    !!
    }  
    32  

    View full-size slide

  32. class Person {!
    !
    name :String = null!
    !
    constructor (name :String) {!
    this.name = name;!
    }!
    !
    getName () :String {!
    return this.name;!
    }!
    !
    }!
    !
    class Doctor extends Person {!
    !
    constructor (name :String) {!
    super('Dr. ' + name);!
    }!
    !
    }  
    33  

    View full-size slide

  33. class Person!
    !
    name: null!
    !
    constructor: (@name) ->!
    !
    getName: () ->!
    @name!
    !
    class Doctor extends Person!
    !
    constructor: (name) ->!
    super('Dr. ' + name)  
    34  

    View full-size slide

  34. var Doctor, Person,!
    __hasProp = {}.hasOwnProperty,!
    __extends = function(child, parent) {!
    for (var key in parent) {!
    if (__hasProp.call(parent, key)) child[key] = parent[key];!
    }!
    function ctor() { this.constructor = child; }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child.__super__ = parent.prototype;!
    return child;!
    };!
    !
    Person = (function() {!
    Person.prototype.name = null;!
    !
    function Person(name) {!
    this.name = name;!
    }!
    !
    Person.prototype.getName = function() {!
    return this.name;!
    };!
    !
    return Person;!
    })();!
    !
    Doctor = (function(_super) {!
    __extends(Doctor, _super);!
    !
    function Doctor(name) {!
    Doctor.__super__.constructor.call(this, 'Dr. ' + name);!
    }!
    !
    return Doctor;!
    })(Person);   35  

    View full-size slide

  35. var Doctor, Person,!
    __hasProp = {}.hasOwnProperty,!
    __extends = function(child, parent) {!
    for (var key in parent) {!
    if (__hasProp.call(parent, key)) child[key] = parent[key];!
    }!
    function ctor() { this.constructor = child; }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child.__super__ = parent.prototype;!
    return child;!
    };!
    !
    Person = (function() {!
    Person.prototype.name = null;!
    !
    function Person(name) {!
    this.name = name;!
    }!
    !
    Person.prototype.getName = function() {!
    return this.name;!
    };!
    !
    return Person;!
    })();!
    !
    Doctor = (function(_super) {!
    __extends(Doctor, _super);!
    !
    function Doctor(name) {!
    Doctor.__super__.constructor.call(this, 'Dr. ' + name);!
    }!
    !
    return Doctor;!
    })(Person);  
    Utility method
    36  

    View full-size slide

  36. var Doctor, Person,!
    __hasProp = {}.hasOwnProperty,!
    __extends = function(child, parent) {!
    for (var key in parent) {!
    if (__hasProp.call(parent, key)) child[key] = parent[key];!
    }!
    function ctor() { this.constructor = child; }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child.__super__ = parent.prototype;!
    return child;!
    };!
    !
    Person = (function() {!
    Person.prototype.name = null;!
    !
    function Person(name) {!
    this.name = name;!
    }!
    !
    Person.prototype.getName = function() {!
    return this.name;!
    };!
    !
    return Person;!
    })();!
    !
    Doctor = (function(_super) {!
    __extends(Doctor, _super);!
    !
    function Doctor(name) {!
    Doctor.__super__.constructor.call(this, 'Dr. ' + name);!
    }!
    !
    return Doctor;!
    })(Person);  
    Class Definitions
    37  

    View full-size slide

  37. var Person = function (name) {!
    this.name = name;!
    };!
    !
    Person.prototype.name = null;!
    !
    Person.prototype.getName = function() {!
    return this.name;!
    };!
    !
    var Doctor = function (name) {!
    Person.call(this, 'Dr. ' + name);!
    };!
    !
    extends(Doctor, Person);  
    38  

    View full-size slide

  38. var Person = function (name) {!
    this.name = name;!
    };!
    !
    Person.prototype.name = null;!
    !
    Person.prototype.getName = function() {!
    return this.name;!
    };!
    !
    var Doctor = function (name) {!
    Person.call(this, 'Dr. ' + name);!
    };!
    !
    extends(Doctor, Person);  
    The magic bit
    39  

    View full-size slide

  39. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };   40  

    View full-size slide

  40. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    Copy static properties / methods
    41  

    View full-size slide

  41. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    Set up prototype chain
    42  

    View full-size slide

  42. var jon = new Person('Jon');!
    var will = new Doctor('Will');!
    !
    jon instanceof Person; // true!
    jon.getName(); // "Jon"!
    !
    will instanceof Person; // true!
    will instanceof Doctor; // true!
    will.getName(); // "Dr. Will"  
    43  

    View full-size slide

  43. Use getter and setter methods
    alert(jon.name);!
    jon.name = 'John';!
    !
    !
    alert(jon.getName());!
    jon.setName('John');!
    jon.set('name', 'John');  
    44  

    View full-size slide

  44. Define all properties on the
    prototype, even if they are null.

    /**!
    * The persons age.!
    * @type {Number}!
    */!
    Person.prototype.age = null;  
    45  

    View full-size slide

  45. Mark private methods with a
    leading or trailing underscore

    // somethings are best kept private :)!
    Person.prototype._singInShower = function () {!
    !
    };  
    46  

    View full-size slide

  46. Use static methods / properties
    when appropriate
    Person.prototype.EVENTS = {!
    WALK: 'WALK',!
    TALK: 'TALK'!
    };!
    !
    Person.EVENTS = {!
    WALK: 'WALK',!
    TALK: 'TALK'!
    };  
    47  

    View full-size slide

  47. topics.shift();!
    "Asynchronous Code"!
     
    48  

    View full-size slide

  48. Callbacks
    or
    Promises
    49  

    View full-size slide

  49. Promises


    •  Requires a library to provide the functionality
    50  

    View full-size slide

  50. Promises


    •  Requires a library to provide the functionality
    •  Different implementations
    •  jQuery Deferred
    •  RSVP.js
    •  when.js
    51  

    View full-size slide

  51. Promises


    •  Requires a library to provide the functionality
    •  Different implementations
    •  jQuery Deferred
    •  RSVP.js
    •  when.js
    •  Kind of complicated…
    52  

    View full-size slide

  52. h-p://promises-­‐aplus.github.io/promises-­‐spec/  
    53  

    View full-size slide

  53. Callback
    Hell
    54  

    View full-size slide

  54. “I’ve come to the conclusion
    that callback hell is a design
    choice and not an inherent
    flaw in the concept of
    asynchronous function and
    callback” http://blog.caplin.com/2013/03/13/callback-hell-is-a-design-choice/
    55  

    View full-size slide

  55. 56  
    load: function () {!
    !
    Api.get('/profile/own', _.bind(function (ownProfile) {!
    !
    this.ownProfile = ownProfile;!
    !
    Api.get('/profile/' + id, _.bind(function (theirProfile) {!
    !
    this.theirProfile = theirProfile;!
    !
    Api.get('/chatMessages', _.bind(function (messages) {!
    !
    this.messages = messages;!
    this.render();!
    !
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }!
    !

    View full-size slide

  56. 57  
    load: function () {!
    !
    Api.get('/profile/own', _.bind(function (ownProfile) {!
    !
    this.ownProfile = ownProfile;!
    !
    Api.get('/profile/' + id, _.bind(function (theirProfile) {!
    !
    this.theirProfile = theirProfile;!
    !
    Api.get('/chatMessages', _.bind(function (messages) {!
    !
    this.messages = messages;!
    this.render();!
    !
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }!
    !

    View full-size slide

  57. 58  
    load: function () {!
    !
    Api.get('/profile/own', _.bind(function (ownProfile) {!
    !
    this.ownProfile = ownProfile;!
    !
    Api.get('/profile/' + id, _.bind(function (theirProfile) {!
    !
    this.theirProfile = theirProfile;!
    !
    Api.get('/chatMessages', _.bind(function (messages) {!
    !
    this.messages = messages;!
    this.render();!
    !
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }!
    !
    Action
    Error Handling

    View full-size slide

  58. doSomething(function (err, response) {!
    !
    });  
    59  

    View full-size slide

  59. 60  
    !
    load: function (id) {!
    this.id = id;!
    Api.get('/profile/own', this.onOwnProfile);!
    },!
    !
    onOwnProfile: function (err, ownProfile) {!
    if (err) return this.onError();!
    this.ownProfile = ownProfile;!
    Api.get('/profile/' + this.id, this.onTheirProfile);!
    },!
    !
    onTheirProfile: function (err, theirProfile) {!
    if (err) return this.onError();!
    this.theirProfile = theirProfile;!
    Api.get('/chatMessages', this.onMessages);!
    },!
    !
    onMessages: function (err, messages) {!
    if (err) return this.onError();!
    this.messages = messages;!
    this.render();!
    }!

    View full-size slide

  60. Avoid anonymous functions
    61  

    View full-size slide

  61. Avoid anonymous functions
    62  
    • Useless  stack  traces  
     

    View full-size slide

  62. Avoid anonymous functions
    63  
    • Useless  stack  traces  
    • Sign  of  poor  structure  

    View full-size slide

  63. Keep things shallow

    64  

    View full-size slide

  64. Keep things shallow

    65  
    • Means  you  are  probably  
    using  anonymous  
    funcGons  

    View full-size slide

  65. Keep things shallow

    66  
    • Means  you  are  probably  
    using  anonymous  
    funcGons  
    • Everyone  will  hate  you  

    View full-size slide

  66. topics.shift();!
    "Performance"!
     
    67  

    View full-size slide

  67. var i = 0;!
    var thing;!
    for (; i < things.length; i++) {!
    thing = things[i];!
    }  
    things.forEach(function (thing, i) {!
    !
    });  
    68  
    or…

    View full-size slide

  68. $('a').on('click', function (e) {!
    !
    });!
    !
    !
    !
    !
    !
    !
    !
    !
    $('#container').on('click', 'a', function (e) {!
    !
    });  
    or…
    69  

    View full-size slide

  69. 70  
    $('#container').append('');!
    for (var i = 0; i < messages.length; i++) {!
    $('#container')!
    .find('ul')!
    .append('' + messages[i].text + '');!
    }  
    !
    !
    !
    !
    !
    var html = '';!
    for (var i = 0; i < messages.length; i++) {!
    html += '' + messages[i].text + '';!
    }!
    html += '';!
    $('#container').html(html);!
    or…

    View full-size slide

  70. 1.DOM operations
    71  

    View full-size slide

  71. 1. DOM operations
    2. Memory management
    72  

    View full-size slide

  72. 1. DOM operations
    2. Memory management
    3. Iteration, function calls
    73  

    View full-size slide

  73. var cache = {!
    !
    get: function (key) {!
    return localStorage.getItem(key);!
    },!
    !
    set: function (key, value) {!
    localStorage.setItem(key, value);!
    }!
    !
    };  
    74  

    View full-size slide

  74. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   75  

    View full-size slide

  75. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   76  
    Memory

    View full-size slide

  76. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   77  
    Quicker reading

    View full-size slide

  77. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   78  
    Saving for later

    View full-size slide

  78. 79  
    topics.shift();!
    undefined  

    View full-size slide

  79. Thank you!

    @jonbretman
    jonbretman.co.uk

    Mobile Web Developer @ Badoo
    www.badoo.com
      80  

    View full-size slide