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  
  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