Advanced-JavaScript icon indicating copy to clipboard operation
Advanced-JavaScript copied to clipboard

Documentation based on John Resig's website on Advanced JavaScript

Advanced-JavaScript

This documentation is based on John Resig's website on Advanced JavaScript. If these concepts seem complex to you, i recommend these resources and also strongly recommend FreeCodeCamp - Learn to code and help nonprofits. If you feel you know most of these concepts, you could just try out the quizzes and you're good to go.

Contents

  • Goal
  • Helper Methods
  • Running a Quiz
  • Lessons
    • Defining Functions
      • Order of function definition
      • Where can assignment be accessed
      • Can functions be defined below return statements
    • Named Functions
      • What is the name of a function
      • With anonymous function that's an object property
      • What happens when we remove the original object
      • Let's give the anonymous function a name
    • Functions as Objects
      • How similar are functions and objects
      • Is it possible to cache the return results from a function
      • One possible way to cache the results
    • Context
      • What happens if a function is an object property
      • What exactly does context represent
      • How can we change the context of a function
      • Different ways of changing the context
      • How can we implement looping with a callback
      • A possible solution for function looping
    • Instantiation
      • What does the new operator do
      • We have a 'this' context that is a Ninja object
      • Add a new property and method to the object
      • What happens when we forget to use the new operator
      • Cont: What happens when we forget to use the new operator
      • We need to make sure that the new operator is always used
      • A solution using arguments.callee
    • Flexible Arguments
      • Using a variable number of arguments to our advantage
      • How can we find the Min/Max number in an array
      • Another possible solution
      • Uh oh, what's going wrong here
      • We can use built-in methods to our advantage
      • We can use call and apply to build a solution
    • Closures
      • But why doesn't this work
      • Closures are frequently used for callbacks
      • They're also useful for timers
      • And they're also frequently used when attaching event listeners
      • Private properties, using closures
      • The last one is quite tricky, we'll revisit it
    • Temporary Scope
      • Self-executing, temporary, function
      • Now we can handle closures and looping
      • The anonymous wrapper functions are also useful for wrapping libraries
      • Another way to wrap a library
      • A quick wrapper function will do the trick
    • Function Prototypes
      • Adding a prototyped method to a function
      • Properties added in the constructor (or later) override prototyped properties
      • Prototyped properties affect all objects of the same constructor, simultaneously, even if they already exist
      • The chainable method must return this
    • Instance Type
      • Examining the basics of an object
      • We can still use the constructor to build other instances
      • Use the .constructor property to dig in
    • Inheritance
      • The basics of how prototypal inheritance works
      • The result is rather straight-forward
    • Built-in Prototypes
      • We can also modify built-in object prototypes
      • Beware: Extending prototypes can be dangerous
    • Enforcing Function Context
      • What happens when we try to bind an object's method to a click handler
      • We need to keep its context as the original object
      • Add a method to all functions to allow context enforcement
      • Our final target (the .bind method from Prototype.js)
    • Bonus: Function Length
      • How does a function's length property work
      • We can use it to implement method overloading
      • How method overloading might work, using the function length property
  • Quizzes
    • QUIZ: Can you cache the results of this function
    • QUIZ: Add a method that gives a name to the ninja
    • QUIZ: Is there another, more generic, way of doing this
    • QUIZ: We must convert array-like objects into actual arrays. Can any built-in methods help
    • QUIZ: Implement a multiplication function (first argument by largest number)
    • QUIZ: What are the values of the variables
    • QUIZ: Fix the broken closures in this loop
    • QUIZ: Make a chainable Ninja method
    • QUIZ: Make another instance of a Ninja
    • QUIZ: Let's try our hand at inheritance
  • Credits
  • Change Logs

Goal

To be able to understand this function:

// The .bind method from Prototype.js
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)));
  };
};

Helper Methods

assert( true, "I'll pass." ); //passes
assert( "truey", "So will I." ); //passes
assert( false, "I'll fail." ); //fails
assert( null, "So will I." ); //fails
log( "Just a simple log", "of", "values.", true );
error( "I'm an error!" );

Running a Quiz

It's not quite hard.

  1. Fork the Repo.
  2. Clone the Repo to your machine.
  3. npm install.
  4. node start.js

Lessons

Defining Functions

Functions can be defined like these:

function isNimble(){ return true; }
var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };
log(isNimble, canFly, isDeadly);

Order of function definition

Order doesn't matter for functions like isNimble.

var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };
assert( isNimble() && canFly() && isDeadly(), "Still works, even though isNimble is moved." );
function isNimble(){ return true; }

Where can assignments be accessed

The Order does matter for canFly and isDeadly.

assert( typeof canFly == "undefined", "canFly doesn't get that benefit." );
assert( typeof isDeadly == "undefined", "Nor does isDeadly." );
var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };

Can functions be defined below return statements

Yes, can be defined but can be called below return statements.

function stealthCheck(){
  assert( stealth(), "We'll never get below the return, but that's OK!" );

  return stealth();

  function stealth(){ return true; }
}

stealthCheck();

Named Functions

We can refer to a function, within itself, by its name.

function yell(n){
  return n > 0 ? yell(n-1) + "a" : "hiy";
}
assert( yell(4) == "hiyaaaa", "Calling the function by itself comes naturally." );

What is the name of a function

var ninja = function myNinja(){
  assert( ninja == myNinja, "This function is named two things - at once!" );
};
ninja();
assert( typeof myNinja == "undefined", "But myNinja isn't defined outside of the function." ); // myNinja exist only within the function scope
log( ninja );

With anonymous function that's an object property

Yeah, we could also have "hiyaaaa" displayed, with an anonymous function that's an Object Property.

var ninja = {
  yell: function(n){
    return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
  }
};
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." );

What happens when we remove the original object

var ninja = {
  yell: function(n){
    return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
  }
};
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." );

var samurai = { yell: ninja.yell };
var ninja = null;

try {
  samurai.yell(4);
} catch(e){
  assert( false, "Uh, this isn't good! Where'd ninja.yell go" );
}

Let's give the anonymous function a name

var ninja = {
  yell: function yell(n){
    return n > 0 ? yell(n-1) + "a" : "hiy";
  }
};
assert( ninja.yell(4) == "hiyaaaa", "Works as we would expect it to!" );

var samurai = { yell: ninja.yell };
var ninja = null;
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." );

What if we don't want to give the function a name

arguments.callee does the trick here :)

var ninja = {
  yell: function(n){
    return n > 0 ? arguments.callee(n-1) + "a" : "hiy";
  }
};
assert( ninja.yell(4) == "hiyaaaa", "arguments.callee is the function itself." );

Functions as Objects

How similar are functions and objects

var obj = {};
var fn = function(){};
assert( obj && fn, "Both the object and function exist." );

More similarity:

var obj = {};
var fn = function(){};
obj.prop = "some value";
fn.prop = "some value";
assert( obj.prop == fn.prop, "Both are objects, both have the property." );

Is it possible to cache the return results from a function

Yes! It is.

function getElements( name ) {
  var results;

  if ( getElements.cache[name] ) {
    results = getElements.cache[name];
  } else {
    results = document.getElementsByTagName(name);
    getElements.cache[name] = results;
  }

  return results;
}
getElements.cache = {};

log( "Elements found: ", getElements("pre").length );
log( "Cache found: ", getElements.cache.pre.length );

QUIZ: Can you cache the results of this function

function isPrime( num ) {
  var prime = num != 1; // Everything but 1 can be prime
  for ( var i = 2; i < num; i++ ) {
    if ( num % i == 0 ) {
      prime = false;
      break;
    }
  }
  return prime;
}

assert( isPrime(5), "Make sure the function works, 5 is prime." );
assert( isPrime.cache[5], "Is the answer cached" );

One possible way to cache the results

function isPrime( num ) {
  if ( isPrime.cache[ num ] != null )
    return isPrime.cache[ num ];

  var prime = num != 1; // Everything but 1 can be prime
  for ( var i = 2; i < num; i++ ) {
    if ( num % i == 0 ) {
      prime = false;
      break;
    }
  }

  isPrime.cache[ num ] = prime

  return prime;
}

isPrime.cache = {};

assert( isPrime(5), "Make sure the function works, 5 is prime." );
assert( isPrime.cache[5], "Make sure the answer is cached." );

Context

What happens if a function is an object property

var katana = {
  isSharp: true,
  use: function(){
    this.isSharp = !this.isSharp;
  }
};
katana.use();
assert( !katana.isSharp, "Verify the value of isSharp has been changed." );

What exactly does context represent

function katana(){
  this.isSharp = true;
}
katana();
assert( isSharp === true, "A global object now exists with that name and value." );

var shuriken = {
  toss: function(){
    this.isSharp = true;
  }
};
shuriken.toss();
assert( shuriken.isSharp === true, "When it's an object property, the value is set within the object." );

How can we change the context of a function

var object = {};
function fn(){
  return this;
}
assert( fn() == this, "The context is the global object." );
assert( fn.call(object) == object, "The context is changed to a specific object." );

Different ways of changing the context

function add(a, b){
  return a + b;
}
assert( add.call(this, 1, 2) == 3, ".call() takes individual arguments" );
assert( add.apply(this, [1, 2]) == 3, ".apply() takes an array of arguments" );

QUIZ: How can we implement looping with a callback

function loop(array, fn){
  for ( var i = 0; i < array.length; i++ ) {
    // Implement me!
  }
}
var num = 0;
loop([0, 1, 2], function(value){
  assert(value == num++, "Make sure the contents are as we expect it.");
  assert(this instanceof Array, "The context should be the full array.");
});

A possible solution for function looping

function loop(array, fn){
  for ( var i = 0; i < array.length; i++ )
    fn.call( array, array[i], i );
}
var num = 0;
loop([0, 1, 2], function(value, i){
  assert(value == num++, "Make sure the contents are as we expect it.");
  assert(this instanceof Array, "The context should be the full array.");
});

Instantiation

What does the new operator do

function Ninja(){
  this.name = "Ninja";
}

var ninjaA = Ninja();
assert( !ninjaA, "Is undefined, not an instance of Ninja." );

var ninjaB = new Ninja();
assert( ninjaB.name == "Ninja", "Property exists on the ninja instance." );

We have a 'this' context that is a Ninja object

function Ninja(){
  this.swung = false;

  // Should return true
  this.swingSword = function(){
    this.swung = !this.swung;
    return this.swung;
  };
}

var ninja = new Ninja();
assert( ninja.swingSword(), "Calling the instance method." );
assert( ninja.swung, "The ninja has swung the sword." );

var ninjaB = new Ninja();
assert( !ninjaB.swung, "Make sure that the ninja has not swung his sword." );

QUIZ: Add a method that gives a name to the ninja

function Ninja(name){
  // Implement!
}

var ninja = new Ninja("John");
assert( ninja.name == "John", "The name has been set on initialization" );

ninja.changeName("Bob");
assert( ninja.name == "Bob", "The name was successfully changed." );

Add a new property and method to the object

function Ninja(name){
  this.changeName = function(name){
    this.name = name;
  };

  this.changeName( name );
}

var ninja = new Ninja("John");
assert( ninja.name == "John", "The name has been set on initialization" );

ninja.changeName("Bob");
assert( ninja.name == "Bob", "The name was successfully changed." );

What happens when we forget to use the new operator

function User(first, last){
  this.name = first + " " + last;
}

var user = User("John", "Resig");
assert( typeof user == "undefined", "Since new wasn't used, the instance is undefined." );

Cont: What happens when we forget to use the new operator

function User(first, last){
  this.name = first + " " + last;
}

window.name = "Resig";
var user = User("John", name);

assert( name == "John Resig", "The name variable is accidentally overridden." );

We need to make sure that the new operator is always used

function User(first, last){
  if ( !(this instanceof User) )
    return new User(first, last);

  this.name = first + " " + last;
}

var name = "Resig";
var user = User("John", name);

assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );

QUIZ: Is there another, more generic, way of doing this

function User(first, last){
  // Replace ___ with a value
  if ( !(this instanceof ___) )
    return new User(first, last);

  this.name = first + " " + last;
}

var name = "Resig";
var user = User("John", name);

assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );

A solution using arguments.callee

function User(first, last){
  if ( !(this instanceof arguments.callee) )
    return new User(first, last);

  this.name = first + " " + last;
}

var name = "Resig";
var user = User("John", name);

assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );

Flexible Arguments

Using a variable number of arguments to our advantage

function merge(root){
  for ( var i = 1; i < arguments.length; i++ )
    for ( var key in arguments[i] )
      root[key] = arguments[i][key];
  return root;
}

var merged = merge({name: "John"}, {city: "Boston"});
assert( merged.name == "John", "The original name is intact." );
assert( merged.city == "Boston", "And the city has been copied over." );

How can we find the Min/Max number in an array

function smallest(array){
  return Math.min.apply( Math, array );
}
function largest(array){
  return Math.max.apply( Math, array );
}
assert(smallest([0, 1, 2, 3]) == 0, "Locate the smallest value.");
assert(largest([0, 1, 2, 3]) == 3, "Locate the largest value.");

Another possible solution

function smallest(){
  return Math.min.apply( Math, arguments );
}
function largest(){
  return Math.max.apply( Math, arguments );
}
assert(smallest(0, 1, 2, 3) == 0, "Locate the smallest value.");
assert(largest(0, 1, 2, 3) == 3, "Locate the largest value.");

Uh oh, what's going wrong here

function highest(){
  return arguments.sort(function(a,b){
    return b - a;
  });
}
assert(highest(1, 1, 2, 3)[0] == 3, "Get the highest value.");
assert(highest(3, 1, 2, 3, 4, 5)[1] == 4, "Verify the results.");

QUIZ: We must convert array-like objects into actual arrays. Can any built-in methods help

// Hint: Arrays have .slice and .splice methods which return new arrays.
function highest(){
  return makeArray(arguments).slice(1).sort(function(a,b){
    return b - a;
  });
}

function makeArray(array){
  // Implement me!
}

// Expecting: [3,2,1]
assert(highest(1, 1, 2, 3)[0] == 3, "Get the highest value.");
// Expecting: [5,4,3,2,1]
assert(highest(3, 1, 2, 3, 4, 5)[1] == 4, "Verify the results.");

We can use built-in methods to our advantage

function highest(){
  return makeArray(arguments).sort(function(a,b){
    return b - a;
  });
}

function makeArray(array){
  return Array().slice.call( array );
}

assert(highest(1, 1, 2, 3)[0] == 3, "Get the highest value.");
assert(highest(3, 1, 2, 3, 4, 5)[1] == 4, "Verify the results.");

QUIZ: Implement a multiplication function (first argument by largest number)

function multiMax(multi){
  // Make an array of all but the first argument
  var allButFirst = ___;

  // Find the largest number in that array of arguments
  var largestAllButFirst = ___;

  // Return the multiplied result
  return multi * largestAllButFirst;
}
assert( multiMax(3, 1, 2, 3) == 9, "3*3=9 (First arg, by largest.)" );

We can use call and apply to build a solution

function multiMax(multi){
  // Make an array of all but the first argument
  var allButFirst = Array().slice.call( arguments, 1 );

  // Find the largest number in that array of arguments
  var largestAllButFirst = Math.max.apply( Math, allButFirst );

  // Return the multiplied result
  return multi * largestAllButFirst;
}
assert( multiMax(3, 1, 2, 3) == 9, "3*3=9 (First arg, by largest.)" );

Closures

A basic closure:

var num = 10;

function addNum(myNum){
  return num + myNum;
}

assert( addNum(5) == 15, "Add two numbers together, one from a closure." );

But why doesn't this work

var num = 10;

function addNum(myNum){
  return num + myNum;
}

num = 15;

assert( addNum(5) == 15, "Add two numbers together, one from a closure." );

Closures are frequently used for callbacks

var results = jQuery("#results").html("<li>Loading...</li>");

jQuery.get("test.html", function(html){
  results.html( html );
  assert( results, "The element to append to, via a closure." );
});

They're also useful for timers

var count = 0;

var timer = setInterval(function(){
  if ( count < 5 ) {
    log( "Timer call: ", count );
    count++;
  } else {
    assert( count == 5, "Count came via a closure, accessed each step." );
    assert( timer, "The timer reference is also via a closure." );
    clearInterval( timer );
  }
}, 100);

And they're also frequently used when attaching event listeners

var count = 1;
var elem = document.createElement("li");
elem.innerHTML = "Click me!";
elem.onclick = function(){
  log( "Click #", count++ );
};
document.getElementById("results").appendChild( elem );
assert( elem.parentNode, "Clickable element appended." );

Private properties, using closures

function Ninja(){
  var slices = 0;

  this.getSlices = function(){
    return slices;
  };
  this.slice = function(){
    slices++;
  };
}

var ninja = new Ninja();
ninja.slice();
assert( ninja.getSlices() == 1, "We're able to access the internal slice data." );
assert( ninja.slices === undefined, "And the private data is inaccessible to us." );

QUIZ: What are the values of the variables

var a = 5;
function runMe(a){
 assert( a == ___, "Check the value of a." );

 function innerRun(){
   assert( b == ___, "Check the value of b." );
   assert( c == ___, "Check the value of c." );
 }

 var b = 7;
 innerRun();
 var c = 8;
}
runMe(6);

for ( var d = 0; d < 3; d++ ) {
 setTimeout(function(){
   assert( d == ___, "Check the value of d." );
 }, 100);
}

The last one is quite tricky, we'll revisit it

var a = 5;
function runMe(a){
 assert( a == 6, "Check the value of a." );

 function innerRun(){
   assert( b == 7, "Check the value of b." );
   assert( c == undefined, "Check the value of c." );
 }

 var b = 7;
 innerRun();
 var c = 8;
}
runMe(6);

for ( var d = 0; d < 3; d++ ) {
 setTimeout(function(){
   assert( d == 3, "Check the value of d." );
 }, 100);
}

Temporary Scope

Self-executing, temporary, function

(function(){
  var count = 0;

  var timer = setInterval(function(){
    if ( count < 5 ) {
      log( "Timer call: ", count );
      count++;
    } else {
      assert( count == 5, "Count came via a closure, accessed each step." );
      assert( timer, "The timer reference is also via a closure." );
      clearInterval( timer );
    }
  }, 100);
})();

assert( typeof count == "undefined", "count doesn't exist outside the wrapper" );
assert( typeof timer == "undefined", "neither does timer" );

Now we can handle closures and looping

for ( var d = 0; d < 3; d++ ) (function(d){
 setTimeout(function(){
   log( "Value of d: ", d );
   assert( d == d, "Check the value of d." );
 }, d * 200);
})(d);

The anonymous wrapper functions are also useful for wrapping libraries

(function(){
  var myLib = window.myLib = function(){
    // Initialize
  };

  // ...
})();

Another way to wrap a library

var myLib = (function(){
  function myLib(){
    // Initialize
  }

  // ...

  return myLib;
})();

QUIZ: Fix the broken closures in this loop

var count = 0;
for ( var i = 0; i < 4; i++ ) {
  setTimeout(function(){
    assert( i == count++, "Check the value of i." );
  }, i * 200);
}

A quick wrapper function will do the trick

var count = 0;
for ( var i = 0; i < 4; i++ ) (function(i){
  setTimeout(function(){
    assert( i == count++, "Check the value of i." );
  }, i * 200);
})(i);

Function Prototypes

Adding a prototyped method to a function

function Ninja(){}

Ninja.prototype.swingSword = function(){
  return true;
};

var ninjaA = Ninja();
assert( !ninjaA, "Is undefined, not an instance of Ninja." );

var ninjaB = new Ninja();
assert( ninjaB.swingSword(), "Method exists and is callable." );

Properties added in the constructor (or later) override prototyped properties

function Ninja(){
  this.swingSword = function(){
    return true;
  };
}

// Should return false, but will be overridden
Ninja.prototype.swingSword = function(){
  return false;
};

var ninja = new Ninja();
assert( ninja.swingSword(), "Calling the instance method, not the prototype method." );

Prototyped properties affect all objects of the same constructor, simultaneously, even if they already exist

function Ninja(){
  this.swung = true;
}

var ninjaA = new Ninja();
var ninjaB = new Ninja();

Ninja.prototype.swingSword = function(){
  return this.swung;
};

assert( ninjaA.swingSword(), "Method exists, even out of order." );
assert( ninjaB.swingSword(), "and on all instantiated objects." );

QUIZ: Make a chainable Ninja method

function Ninja(){
  this.swung = true;
}

var ninjaA = new Ninja();
var ninjaB = new Ninja();

// Add a method to the Ninja prototype which
// returns itself and modifies swung

assert( !ninjaA.swing().swung, "Verify that the swing method exists and returns an instance." );
assert( !ninjaB.swing().swung, "and that it works on all Ninja instances." );

The chainable method must return this

function Ninja(){
  this.swung = true;
}

var ninjaA = new Ninja();
var ninjaB = new Ninja();

Ninja.prototype.swing = function(){
  this.swung = false;
  return this;
};

assert( !ninjaA.swing().swung, "Verify that the swing method exists and returns an instance." );
assert( !ninjaB.swing().swung, "and that it works on all Ninja instances." );

Instance Type

Examining the basics of an object

function Ninja(){}

var ninja = new Ninja();

assert( typeof ninja == "object", "However the type of the instance is still an object." );
assert( ninja instanceof Ninja, "The object was instantiated properly." );
assert( ninja.constructor == Ninja, "The ninja object was created by the Ninja function." );

We can still use the constructor to build other instances

function Ninja(){}
var ninja = new Ninja();
var ninjaB = new ninja.constructor();

assert( ninjaB instanceof Ninja, "Still a ninja object." );

QUIZ: Make another instance of a Ninja

var ninja = (function(){
 function Ninja(){}
 return new Ninja();
})();

// Make another instance of Ninja
var ninjaB = ___;

assert( ninja.constructor == ninjaB.constructor, "The ninjas come from the same source." );

Use the .constructor property to dig in

var ninja = (function(){
 function Ninja(){}
 return new Ninja();
})();

// Make another instance of Ninja
var ninjaB = new ninja.constructor();

assert( ninja.constructor == ninjaB.constructor, "The ninjas come from the same source." );

Inheritance

The basics of how prototypal inheritance works

function Person(){}
Person.prototype.dance = function(){};

function Ninja(){}

// Achieve similar, but non-inheritable, results
Ninja.prototype = Person.prototype;
Ninja.prototype = { dance: Person.prototype.dance };

assert( (new Ninja()) instanceof Person, "Will fail with bad prototype chain." );

// Only this maintains the prototype chain
Ninja.prototype = new Person();

var ninja = new Ninja();
assert( ninja instanceof Ninja, "ninja receives functionality from the Ninja prototype" );
assert( ninja instanceof Person, "... and the Person prototype" );
assert( ninja instanceof Object, "... and the Object prototype" );

QUIZ: Let's try our hand at inheritance

function Person(){}
Person.prototype.getName = function(){
  return this.name;
};

// Implement a function that inherits from Person
// and sets a name in the constructor

var me = new Me();
assert( me.getName(), "A name was set." );

The result is rather straight-forward

function Person(){}
Person.prototype.getName = function(){
  return this.name;
};

function Me(){
  this.name = "John Resig";
}
Me.prototype = new Person();

var me = new Me();
assert( me.getName(), "A name was set." );

Built-in Prototypes

We can also modify built-in object prototypes

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(fn){
    for ( var i = 0; i < this.length; i++ ) {
      fn( this[i], i, this );
    }
  };
}

["a", "b", "c"].forEach(function(value, index, array){
  assert( value, "Is in position " + index + " out of " + (array.length - 1) );
});

Beware: Extending prototypes can be dangerous

Object.prototype.keys = function(){
  var keys = [];
  for ( var i in this )
    keys.push( i );
  return keys;
};

var obj = { a: 1, b: 2, c: 3 };

assert( obj.keys().length == 3, "We should only have 3 properties." );

delete Object.prototype.keys;

Enforcing Function Context

What happens when we try to bind an object's method to a click handler

var Button = {
  click: function(){
    this.clicked = true;
  }
};

var elem = document.createElement("li");
elem.innerHTML = "Click me!";
elem.onclick = Button.click;
document.getElementById("results").appendChild(elem);

elem.onclick();
assert( elem.clicked, "The clicked property was accidentally set on the element" );

We need to keep its context as the original object

function bind(context, name){
  return function(){
    return context[name].apply(context, arguments);
  };
}

var Button = {
  click: function(){
    this.clicked = true;
  }
};

var elem = document.createElement("li");
elem.innerHTML = "Click me!";
elem.onclick = bind(Button, "click");
document.getElementById("results").appendChild(elem);

elem.onclick();
assert( Button.clicked, "The clicked property was correctly set on the object" );

Add a method to all functions to allow context enforcement

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

var Button = {
  click: function(){
    this.clicked = true;
  }
};

var elem = document.createElement("li");
elem.innerHTML = "Click me!";
elem.onclick = Button.click.bind(Button);
document.getElementById("results").appendChild(elem);

elem.onclick();
assert( Button.clicked, "The clicked property was correctly set on the object" );

Our final target (the .bind method from Prototype.js)

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 Button = {
  click: function(value){
    this.clicked = value;
  }
};

var elem = document.createElement("li");
elem.innerHTML = "Click me!";
elem.onclick = Button.click.bind(Button, false);
document.getElementById("results").appendChild(elem);

elem.onclick();
assert( Button.clicked === false, "The clicked property was correctly set on the object" );

Bonus: Function Length

How does a function's length property work

function makeNinja(name){}
function makeSamurai(name, rank){}
assert( makeNinja.length == 1, "Only expecting a single argument" );
assert( makeSamurai.length == 2, "Multiple arguments expected" );

We can use it to implement method overloading

function addMethod(object, name, fn){
  // Save a reference to the old method
  var old = object[ name ];

  // Overwrite the method with our new one
  object[ name ] = function(){
    // Check the number of incoming arguments,
    // compared to our overloaded function
    if ( fn.length == arguments.length )
      // If there was a match, run the function
      return fn.apply( this, arguments );

    // Otherwise, fallback to the old method
    else if ( typeof old === "function" )
      return old.apply( this, arguments );
  };
}

How method overloading might work, using the function length property

function addMethod(object, name, fn){
  // Save a reference to the old method
  var old = object[ name ];

  // Overwrite the method with our new one
  object[ name ] = function(){
    // Check the number of incoming arguments,
    // compared to our overloaded function
    if ( fn.length == arguments.length )
      // If there was a match, run the function
      return fn.apply( this, arguments );

    // Otherwise, fallback to the old method
    else if ( typeof old === "function" )
      return old.apply( this, arguments );
  };
}

function Ninjas(){
  var ninjas = [ "Dean Edwards", "Sam Stephenson", "Alex Russell" ];
  addMethod(this, "find", function(){
    return ninjas;
  });
  addMethod(this, "find", function(name){
    var ret = [];
    for ( var i = 0; i < ninjas.length; i++ )
      if ( ninjas[i].indexOf(name) == 0 )
        ret.push( ninjas[i] );
    return ret;
  });
  addMethod(this, "find", function(first, last){
    var ret = [];
    for ( var i = 0; i < ninjas.length; i++ )
      if ( ninjas[i] == (first + " " + last) )
        ret.push( ninjas[i] );
    return ret;
  });
}

var ninjas = new Ninjas();
assert( ninjas.find().length == 3, "Finds all ninjas" );
assert( ninjas.find("Sam").length == 1, "Finds ninjas by first name" );
assert( ninjas.find("Dean", "Edwards").length == 1, "Finds ninjas by first and last name" );
assert( ninjas.find("Alex", "X", "Russell") == null, "Does nothing" );

Credits

John Resig - He made the whole thing.

Change Logs

Contribution

Contributions are highly welcome

License

ISC