JavaScript So Easy

Even a Caveman Can Do It

Dan Shultz • OverDrive, Inc

JavaScript Easy?

Maybe it is hard...

...or just misunderstood.

Quiz

Whats the output of console.log?

              
              > var foo = 1;
                function bar() {
                  if (!foo) {
                    var foo = 10;
                  }
                  console.log(foo);
                }
              > bar();
                10
              
            
                            // What you wrote
              var foo = 1;
              function bar() {
                if (!foo) {
                  var foo = 10;
                }
                console.log(foo);
              }
              // What you really wrote
              var foo = 1;
              function bar() {
                var foo;
                if (!foo) {
                  foo = 10;
                }
                console.log(foo);
              }
              
              
            

Understanding: scoping

Block Level Scoping


            // c#
            if(true) {
              int foo = 15;
            }

            foo++; // compiler error
            

Function Level Scoping

only functions create a new scope*


            > var foo = 1;
              function bar() {
                if (!foo) {
                  (function () {
                    var foo = 10;
                  })();
                } 
                console.log(foo);
              }
            > bar();
              1
            

Understanding: Hoisting

Variable Hoisting

              
              function () {
                foo();
                var bar = "baz";
              }

              // is synonymous to above
              function () {
                var bar;
                foo();
                bar = "baz";
              }
              
              
            

Function Hoisting

              
(function () {
  foo(); // TypeError: undefined is not a function
  bar(); // barrrr!
  
  var foo = function () { console.log('foo!!!'); }
  function bar () { console.log('barrrr!'); }
})();
              
          	

Named Function Expressions

              
(function () {
  foo(); // TypeError: undefined is not a function
  bar(); // barrrr!
  baz(); // TypeError: undefined is not a function
  pony(); // ReferenceError: pony is not defined

  var foo = function () { console.log('foo!!!'); } 
  function bar () { console.log('barrrr!'); } 
  var baz = function pony () { console.log('ride it!'); } 

  foo(); // foo!!!
  bar(); // barrrr!
  baz(); // ride it!
  pony(); // ReferenceError: pony is not defined
})();
              
          	

When scoping goes bad

Scoping Gone Bad

              
  function f() { return 'global' }

  function foo(x) {
    var results = [];
    results.push(f());
    if (x) {
      function f() { return 'local' }
      results.push(f());
    }
    return results;
  }
              
          	

Scoping Gone Bad

              
          //chrome 23.0.x
          > foo(true);
            ["local", "local"]
          > foo(false);
            ["local"]
              
          	

Scoping Gone Bad

              
          //firefox 16.x
          > foo(true);
            ["global", "local"]
          > foo(false);
            ["global"]
              
          	

Scoping Gone Bad

              
      // To avoid confusion and weird bugs...
      // use var to declare functions

      function foo() { ... }
      function bar(x) {
        var y = foo;
        if(x) {
          y = function () { ... }
        }
        y();
      }
              
            

Understanding: Prototypes

"new" keyword

              
      var Animal = function Animal(name, says) {
        this.name = name;
        this.says = says;
      };

      var cat = new Animal("fluffy", "meow");

      Object.getPrototypeOf(cat); // cat.__proto__
        Animal
              
            

"prototype" Property

              
      Animal.prototype.speak = function speak() {
        console.log(this.name, "says", this.says);
      };
      
      var cat = new Animal("fluffy", "meow");

      > cat.speak();
        fluffy says meow

      Animal.prototype.move = function move() {
        console.log(this.name, "walks");
      }

      > cat.move();
        fluffy walks
              
            

Prototypes via Object.create

              
          > var animal = Object.create(null);
          > Object.getPrototypeOf(animal);
            null
          
          > var animal = Object.create(null);
          > Object.getPrototypeOf(animal); 
            Object

              
            

Understanding: Function Invocations

Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )

Function.prototype.call

              
        > function hello (world) {
          console.log(this + ' says hello', world);
        }

        > hello.call("dan", "world");
        dan says hello world
              
          	

Function.prototype.call

              
        function say () {
          console.log('the', this.type, 'says', this.noise);
        }
        var cow = { type: 'cow', noise: 'moo' };

        say.call(cow); // the cow says moo
        cow.say = say;
        cow.say(); // the cow says moo
              
          	

Function.prototype.apply (thisArg, argArray)

Function.prototype.apply

              
        > Math.min(1, 2, 3, 4, 5);
          1

        > Math.min.apply(Math, [1, 2, 3, 4, 5]);
          1
              
          	

Why This is Powerful

              
_slice = [].slice;

log = function () {
  var args = _slice.call(arguments, 0);

  if (this.logPrefix) {
    args.unshift(this.logPrefix);
  }
  if (window.console && typeof console.log === 'function') {
    console['log'].apply(console, args);
  }
}
              
          	

function.bind - controlling "this"

              
  var animal = {
    name: 'cow',
    speak: function () { console.log(this.name, 'says moo'); }
  };

  var speaker = animal.speak;
  speaker(); // undefined says moo
  
  speaker = animal.speak.bind(animal);
  speaker(); // cow says moo
  
              
          	

function.bind - partial application

              
function buildUrl(protocol, domain, path) {
  return protocol + "://" + domain + "/" + path;
}

// instead of this
var urls = paths.map(function (path) {
  return buildUrl("http", "www.mydomain.com", path);
}

// use this
var urlBuilder = buildUrl.bind(null, "http", "www.mydomain.com");
var urls = paths.map(urlBuilder);
              
          	

Handling the Async

Embrace Eventing

  • Models Real Life Interactions
  • Promotes Decoupling
  • Abstracts Implementation
  • Flexible

Callbacks...

...don't

Pyramid of Doom

            
  (function($) {
    $(function(){
      $("button").click(function(e) {
        $.get("/test.json", function(data, textStatus, jqXHR) {
          $(".list").each(function() {
            $(this).click(function(e) {
              setTimeout(function() {
                alert("Hello World!");
              }, 1000);
            });
          });
        });
      });
    });
  })(jQuery);
            
            

Why no callback love

  • Limits handling of event
  • Awkward error handling
  • Callback leaks through out your abstractions
  • Lots of better, more flexible alternatives
              
FoodService.prototype.getLunch = function (order, callback) {
  //.. go to the store
  callback("here's your lunch");
}

// instead, maybe
FoodService.prototype.getLunch = function (order) {
  //.. go to the store
  this.trigger("hasFood", "here's your lunch");
}
              
              
          	

...or return a promise

              
FoodService.prototype.getLunch = function getLunch(order) {
  var deferred = this._getLunchPromise = $.Deferred();
  //.. set off to do stuff and resolve/fail the promise later
  return deferred.promise();
}
              
          	

Promises

Promises

  • Allow you to return a value or throw an exception without blocking
  • Provides a proxy for a remote object to manage latency
  • Mitigate Pyramid of Doom

Promises/A

then(fulfilledHandler, errorHandler, progressHandler)

Hows that help

              
    step1(function (value1) {
        step2(value1, function(value2) {
            step3(value2, function(value3) {
                step4(value3, function(value4) {
                    // Do something with value4
                });
            });
        });
    });

   // With Promises
   step1.
    then(step2).
    then(step3).
    then(step4, orError); // notice onError at the end
              
          	

Create a new promise with jQuery

                function getPromise () {
    var d = $.Deferred();
    setTimeout(d.resolve, 100, "My Resolution);
    return d.promise();
  }

  getPromise().
    then(console.log) // print "My Resolution" after 100ms
              
          	

jQuery Ajax

              
    getBooks = -> $.ajax(url: "api.com/books") 
    getAuthors = (books) -> 
      # mapping function to match the authors to their 
      # respective books
      mapAuthorsToBooks = (authors...) ->
        books[i].authro = author[0] for author, i in authors
        books

      # map each book to a new ajax request
      newPromises = books.map((b) -> $.ajax(b.authorUri))

      # create a new promise with jQuery when
      return $.when.apply($, newPromises).pipe(mapBooksToAuthors);
  

              
          	

jQuery Ajax

              
    getBooks.
      then(getAuthors).
      then(doStuff, handleError);
              
          	

Even more eventing strategies

Global Event Bus

  • Necessary to facilitate communication between multiple parts of system.
  • Think RabbitMQ, NServiceBus, etc
    • Queueing
    • Global Failures
    • Routing
              
  global.MyApp.GrandCentralDispatch
    .on("cart.checkOutCompleted", LetMeKnow);
              
            

Proxy Events

              class InboxController
  constructor: (messages) ->
    this._listController = new ListController(messages)
    this._listController.on(
      'item-selected', this._messageSelected
    )

  _messageSelected: (message)->
    this.trigger('message-selected', message)


var inboxController = new InboxController(messages)
inboxController.on('message-selected', doSomething)
              
          	

Protip: Namespace Events

              
  button.on('click.my-name-space', onClickHandler);

  // Easily unbind your click handlers without disturbing others
  button.off('click.my-name-space');
              
          	

Thanks - any questions?

Dan Shultz • OverDrive, Inc

@danshultz