Ask Not What JavaScript Can Do For You

0324ece38e4ae25a75c52f2bbec7967a?s=47 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.

0324ece38e4ae25a75c52f2bbec7967a?s=128

Jon Bretman

September 14, 2013
Tweet

Transcript

  1. 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  
  2. 4.
  3. 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);! ! });! 6  
  4. 7.

    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  
  5. 8.

    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  
  6. 9.

    if (data.value) {! // do something with value! }! !

    ! TypeError: Cannot read property 'value' of null! ! ! ! !       9  
  7. 10.

    if (data && data.value) {! var index = data.value.indexOf('something');! //

    do something with index! }! ! ! ! TypeError: Object #<Object> has no method ‘indexOf’! ! ! ! ! !     10  
  8. 11.

    if (data && data.callback) {! var result = data.callback();! //

    do something with result! }! ! ! TypeError: Property 'callback' of object #<Object> is not a function     11  
  9. 12.

    typeof {};! "object"! ! typeof 'hello';! "string"! ! typeof 5;!

    "number”! typeof function () {};! "function"! ! typeof undefined;! "undefined"! ! typeof true;! "boolean"! ! 12  
  10. 13.

    typeof [];! "object"! ! typeof null;! "object"! ! typeof new

    Date();! "object"! typeof /jsconf/;! "object"! ! typeof document.body;! "object"! ! typeof NaN;! "number"! ! 13  
  11. 16.

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

    •  If the this value is null, return "[object Null]".! 16  
  12. 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.! ! ! 17  
  13. 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 "]".! ! 18  
  14. 19.

    •  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  
  15. 20.

    var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! var match = toString.call(o).match(regex);! return match[1].toLowerCase();! };! 20  
  16. 21.

    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  
  17. 22.

    type({});! "object"! ! type('hello');! "string"! ! type(5);! "number”! type(function ()

    {});! "function"! ! type(undefined);! "undefined"! ! type(true);! "boolean"! ! 22  
  18. 23.

    type([]);! "array"! ! type(null);! "null"! ! type(new Date());! "date"! type(/jsconf/);!

    "regex"! ! type(document.body);! "htmlbodyelement"! ! type(NaN);! "number"! ! 23  
  19. 24.

    type([]);! "array"! ! type(null);! "null"! ! type(new Date());! "date"! type(/jsconf/);!

    "regex"! ! type(document.body);! "htmlbodyelement"! ! type(NaN);! "number"! ! ??? 24  
  20. 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;! };! 25  
  21. 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;! };! 26   Special case for DOM elements
  22. 27.

    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
  23. 28.

    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  
  24. 29.

    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  
  25. 30.

    Check for types where not doing so could cause an

    exception or make code ambiguous 30  
  26. 32.

    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  
  27. 33.

    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  
  28. 34.

    class Person! ! name: null! ! constructor: (@name) ->! !

    getName: () ->! @name! ! class Doctor extends Person! ! constructor: (name) ->! super('Dr. ' + name)   34  
  29. 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);   35  
  30. 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);   Utility method 36  
  31. 37.

    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  
  32. 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);   38  
  33. 39.

    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  
  34. 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;! };   40  
  35. 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;! };   Copy static properties / methods 41  
  36. 42.

    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  
  37. 43.

    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  
  38. 44.

    Use getter and setter methods alert(jon.name);! jon.name = 'John';! !

    ! alert(jon.getName());! jon.setName('John');! jon.set('name', 'John');   44  
  39. 45.

    Define all properties on the prototype, even if they are

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

    Mark private methods with a leading or trailing underscore //

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

    Use static methods / properties when appropriate Person.prototype.EVENTS = {!

    WALK: 'WALK',! TALK: 'TALK'! };! ! Person.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'! };   47  
  42. 51.

    Promises •  Requires a library to provide the functionality • 

    Different implementations •  jQuery Deferred •  RSVP.js •  when.js 51  
  43. 52.

    Promises •  Requires a library to provide the functionality • 

    Different implementations •  jQuery Deferred •  RSVP.js •  when.js •  Kind of complicated… 52  
  44. 55.

    “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  
  45. 56.

    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));! }! !
  46. 57.

    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));! }! !
  47. 58.

    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
  48. 60.

    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();! }!
  49. 66.

    Keep things shallow 66   • Means  you  are  probably  

    using  anonymous   funcGons   • Everyone  will  hate  you  
  50. 68.

    var i = 0;! var thing;! for (; i <

    things.length; i++) {! thing = things[i];! }   things.forEach(function (thing, i) {! ! });   68   or…
  51. 69.

    $('a').on('click', function (e) {! ! });! ! ! ! !

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

    70   $('#container').append('<ul></ul>');! for (var i = 0; i <

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

    var cache = {! ! get: function (key) {! return

    localStorage.getItem(key);! },! ! set: function (key, value) {! localStorage.setItem(key, value);! }! ! };   74  
  54. 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);! }! ! };   75  
  55. 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);! }! ! };   76   Memory
  56. 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);! }! ! };   77   Quicker reading
  57. 78.

    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