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. 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. var topics = [! 'Type Checking',! 'Classes and Inheritance’,! 'Asynchronous

    Code',! 'Performance'! ];!   2  
  3. topics.shift();! "Type Checking"!   3  

  4. 4  

  5. <!doctype html>! <html>! <head>! <title>My Awesome App</title>! <script src="App.js"></script>! </head>!

    <body>! </body>! </html>   5  
  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  
  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  
  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  
  9. if (data.value) {! // do something with value! }! !

    ! TypeError: Cannot read property 'value' of null! ! ! ! !       9  
  10. if (data && data.value) {! var index = data.value.indexOf('something');! //

    do something with index! }! ! ! ! TypeError: Object #<Object> has no method ‘indexOf’! ! ! ! ! !     10  
  11. if (data && data.callback) {! var result = data.callback();! //

    do something with result! }! ! ! TypeError: Property 'callback' of object #<Object> is not a function     11  
  12. typeof {};! "object"! ! typeof 'hello';! "string"! ! typeof 5;!

    "number”! typeof function () {};! "function"! ! typeof undefined;! "undefined"! ! typeof true;! "boolean"! ! 12  
  13. typeof [];! "object"! ! typeof null;! "object"! ! typeof new

    Date();! "object"! typeof /jsconf/;! "object"! ! typeof document.body;! "object"! ! typeof NaN;! "number"! ! 13  
  14. Object.prototype.toString()!         14  

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

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

    •  If the this value is null, return "[object Null]".! 16  
  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  
  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  
  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  
  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  
  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  
  22. type({});! "object"! ! type('hello');! "string"! ! type(5);! "number”! type(function ()

    {});! "function"! ! type(undefined);! "undefined"! ! type(true);! "boolean"! ! 22  
  23. type([]);! "array"! ! type(null);! "null"! ! type(new Date());! "date"! type(/jsconf/);!

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

    "regex"! ! type(document.body);! "htmlbodyelement"! ! type(NaN);! "number"! ! ??? 24  
  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  
  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
  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
  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  
  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  
  30. Check for types where not doing so could cause an

    exception or make code ambiguous 30  
  31. topics.shift();! "Classes and Inheritance"!   31  

  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  
  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  
  34. class Person! ! name: null! ! constructor: (@name) ->! !

    getName: () ->! @name! ! class Doctor extends Person! ! constructor: (name) ->! super('Dr. ' + name)   34  
  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  
  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  
  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  
  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  
  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  
  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  
  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  
  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  
  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  
  44. Use getter and setter methods alert(jon.name);! jon.name = 'John';! !

    ! alert(jon.getName());! jon.setName('John');! jon.set('name', 'John');   44  
  45. Define all properties on the prototype, even if they are

    null. /**! * The persons age.! * @type {Number}! */! Person.prototype.age = null;   45  
  46. Mark private methods with a leading or trailing underscore //

    somethings are best kept private :)! Person.prototype._singInShower = function () {! ! };   46  
  47. Use static methods / properties when appropriate Person.prototype.EVENTS = {!

    WALK: 'WALK',! TALK: 'TALK'! };! ! Person.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'! };   47  
  48. topics.shift();! "Asynchronous Code"!   48  

  49. Callbacks or Promises 49  

  50. Promises •  Requires a library to provide the functionality 50

     
  51. Promises •  Requires a library to provide the functionality • 

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

    Different implementations •  jQuery Deferred •  RSVP.js •  when.js •  Kind of complicated… 52  
  53. h-p://promises-­‐aplus.github.io/promises-­‐spec/   53  

  54. Callback Hell 54  

  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  
  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));! }! !
  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));! }! !
  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
  59. doSomething(function (err, response) {! ! });   59  

  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();! }!
  61. Avoid anonymous functions 61  

  62. Avoid anonymous functions 62   • Useless  stack  traces    

  63. Avoid anonymous functions 63   • Useless  stack  traces   • Sign

     of  poor  structure  
  64. Keep things shallow 64  

  65. Keep things shallow 65   • Means  you  are  probably  

    using  anonymous   funcGons  
  66. Keep things shallow 66   • Means  you  are  probably  

    using  anonymous   funcGons   • Everyone  will  hate  you  
  67. topics.shift();! "Performance"!   67  

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

    things.length; i++) {! thing = things[i];! }   things.forEach(function (thing, i) {! ! });   68   or…
  69. $('a').on('click', function (e) {! ! });! ! ! ! !

    ! ! ! ! $('#container').on('click', 'a', function (e) {! ! });   or… 69  
  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…
  71. 1.DOM operations 71  

  72. 1. DOM operations 2. Memory management 72  

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

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

    localStorage.getItem(key);! },! ! set: function (key, value) {! localStorage.setItem(key, value);! }! ! };   74  
  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  
  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
  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
  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
  79. 79   topics.shift();! undefined  

  80. Thank you! @jonbretman jonbretman.co.uk Mobile Web Developer @ Badoo www.badoo.com

      80