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

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

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

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

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

    Date();! "object"! typeof /jsconf/;! "object"! ! typeof document.body;! "object"! ! typeof NaN;! "number"! ! 13  
  10. •  If the this value is undefined, return "[object Undefined]".!

    •  If the this value is null, return "[object Null]".! 16  
  11. •  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  
  12. •  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  
  13. •  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  
  14. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! var match = toString.call(o).match(regex);! return match[1].toLowerCase();! };! 20  
  15. 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  
  16. type({});! "object"! ! type('hello');! "string"! ! type(5);! "number”! type(function ()

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

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

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

    exception or make code ambiguous 30  
  25. 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  
  26. 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  
  27. class Person! ! name: null! ! constructor: (@name) ->! !

    getName: () ->! @name! ! class Doctor extends Person! ! constructor: (name) ->! super('Dr. ' + name)   34  
  28. 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  
  29. 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  
  30. 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  
  31. 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  
  32. 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  
  33. 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  
  34. 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  
  35. 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  
  36. 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  
  37. Use getter and setter methods alert(jon.name);! jon.name = 'John';! !

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

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

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

    WALK: 'WALK',! TALK: 'TALK'! };! ! Person.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'! };   47  
  41. Promises •  Requires a library to provide the functionality • 

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

    Different implementations •  jQuery Deferred •  RSVP.js •  when.js •  Kind of complicated… 52  
  43. “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  
  44. 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));! }! !
  45. 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));! }! !
  46. 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
  47. 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();! }!
  48. Keep things shallow 66   • Means  you  are  probably  

    using  anonymous   funcGons   • Everyone  will  hate  you  
  49. var i = 0;! var thing;! for (; i <

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

    ! ! ! ! $('#container').on('click', 'a', function (e) {! ! });   or… 69  
  51. 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…
  52. var cache = {! ! get: function (key) {! return

    localStorage.getItem(key);! },! ! set: function (key, value) {! localStorage.setItem(key, value);! }! ! };   74  
  53. 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  
  54. 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
  55. 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
  56. 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