Book Notes: Secrets of the Javascript Ninjas

This post will cover chapters 3 and 4 of “Secrets of the JavaScript Ninjas”, by John Resig. It will not be an exhaustive set of notes, but rather only the important points that I want to remember.

Scoping and Functions

Scopes in JavaScript are declared by functions, not by blocks. A variable declared inside a block does not terminate when the block ends.

Named functions are in scope within the entire function they’re declared in.

Function Invocation

Excess arguments (not matching function parameters) are silently discarded.

Parameters with no corresponding arguments are set to undefined.

All function invocations get 2 extra parameters: this and arguments.

The arguments parameter is an array-like object (accessed by index) of all passed arguments. It is not a real array. It has a property length like an array, and a property callee which is a reference to the function itself.

Note that callee is deprecated as of ECMAscript 5.

The this parameter is the function context for the invocation and its value depends on how the function was invoked.

Invocation as a function

    function foo() {
  
    }
    foo();

this is the global context (e.g. the window object)

Invocation as a method

    var obj = { foo: function() {} };
    obj.foo();

this is the object containing foo, e.g. obj

Invocation as a constructor

    function Ninja() { 
      this.foo = function() { return this; }
    }
    var a = new Ninja();
    a.foo();

this is a brand new object passed to the constructor (and returned from it implicitly if no explicit return is called).

Invocation with apply() and call()

    function foo() {
  
    }
    foo.apply(obj, 1, 2, 3)

this is whatever object is explicitly passed in along with the arguments.

Inline Functions

    var ninja = {
      chirp: function signal(n) {
     return n > 1 ? signal(n - 1) + "-chirp" : "chirp";
      } 
    };

Useful for recursive calls, since it divorces the function invocation from the name of the property.

Note that the name does not exist outside the inline function itself.

Functions can have properties

    function foo() {
      return arguments.callee.bar;
    }
    foo.bar = 1234;
    foo()

Can be used to create self-memoizing functions:

    function foo(x) {
      var me = arguments.callee;
      if (!me.cache) { me.cache = {}; };

      if (me.cache[x] != null) {
        return me.cache[x];
      }
      return me.cache[x] = expensive_operation();
    }

Function overloading

How to add methods to an object that do different things based on the number of arguments passed in:

    function addMethod(object, name, fn) {
      var old = object[name];
      object[name] = function(){
        if (fn.length == arguments.length)
          return fn.apply(this, arguments)
        else if (typeof old == 'function')
          return old.apply(this, arguments);
      };
    }
    var ninja = {};
    addMethod(ninja,'whatever',function(){ /* do something */ });
    addMethod(ninja,'whatever',function(a){ /* do something else */ });
    addMethod(ninja,'whatever',function(a,b){ /* yet something else */ });

Check for a function’s existence

    function isFunction(fn) {
      return Object.prototype.toString.call(fn) === "[object Function]";
    }

Binding a function to a specific context

    // Exists natively in JavaScript 1.8.5
    if (Function.prototype.bind === undefined) {
      Function.prototype.bind = function(){
        var fn = this, args = Array.prototype.slice.call(arguments),
          object = args.shift();
        return function(){
          return fn.apply(object,
            args.concat(Array.prototype.slice.call(arguments)));
        };
      };
    }
    var o = {};
    var f = function() {...}.bind(o);

Partially applied functions

    Function.prototype.partial = function() {
      var fn = this, args = Array.prototype.slice.call(arguments);
      return function() {
        var arg = 0;
        for (var i = 0; i < args.length && arg < arguments.length; i++) {
          if (args[i] === undefined) {
            args[i] = arguments[arg++];
          } 
        }
        return fn.apply(this, args);
      };
    };
    var delay10seconds = setTimeout.partial(undefined, 10);
    delay10seconds(function() {
      alert("Hi!")
    });

    var bindBodyClick = document.body.addEventListener.partial(
      "click", undefined, false);
    bindBodyClick(function() {
      alert("Hi!")
    });

Using a closure to memoize functions

    Function.prototype.memoize = function(){
        var fn = this;
        return function(){
          return fn.memoized.apply( fn, arguments );
        };
    };

Wrapping a function

    function wrap(obj, method, wrapper) {
      var fn = object[method];   // save old function
      
      return object[method] = function() {
        return wrapper.apply(this, [fn.bind(this)].concat (
          Array.prototype.slice.call(arguments)));
      };
    }

Immediate functions and JQuery safety

    (function($) {
      
      // Now we can safely assume $ is jQuery no matter what other code
      // is included in the page that might redefine it.
      $('foo').bar();
      
    })(jQuery);

Also useful for doing a bunch of work on a really long reference name:

    (function(v) {
      
      v.foo()
      v.bar()
      v.baz()
      
    })(really.long.object.reference.thats.a.pain.to.use)

Can work around closure issues in loops

    for (var i=0; i < 10; i++) (function(n) {

      // n will now always be the loop counter's correct value

    })(i);

Useful for library creation

(function () {
  var jQuery = window.jQuery = function() {

    // our local 'jQuery' will always be consistent, no matter what happens
    // outside to the global version.
    
  };
})();