Skip to content

Jacky-odoo/Advanced-JavaScript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Advanced-JavaScript

This documentation is based on John Resig's website on Advanced JavaScript

Content

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!" );

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" );

About

Documentation based on John Resig's website on Advanced JavaScript

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 100.0%