2018-02-23 18:58:03 +00:00
|
|
|
---
|
|
|
|
created_at: '2016-12-11T04:47:14.000Z'
|
|
|
|
title: Learning Advanced JavaScript (2008)
|
|
|
|
url: http://ejohn.org/apps/learn/
|
|
|
|
author: kparaju
|
|
|
|
points: 192
|
|
|
|
story_text:
|
|
|
|
comment_text:
|
|
|
|
num_comments: 55
|
|
|
|
story_id:
|
|
|
|
story_title:
|
|
|
|
story_url:
|
|
|
|
parent_id:
|
|
|
|
created_at_i: 1481431634
|
|
|
|
_tags:
|
|
|
|
- story
|
|
|
|
- author_kparaju
|
|
|
|
- story_13149635
|
|
|
|
objectID: '13149635'
|
|
|
|
|
|
|
|
---
|
2018-02-23 18:19:40 +00:00
|
|
|
[Source](https://johnresig.com/apps/learn/ "Permalink to Learning Advanced JavaScript")
|
|
|
|
|
|
|
|
# Learning Advanced JavaScript
|
|
|
|
|
|
|
|
### Learning Advanced JavaScript
|
|
|
|
|
|
|
|
Double-click the code to edit the tutorial and try your own code.
|
|
|
|
This tutorial contains code and discussion from the upcoming book [Secrets of the JavaScript Ninja][1] by [John Resig][2].
|
|
|
|
|
|
|
|
Our Goal
|
|
|
|
:
|
|
|
|
|
|
|
|
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)));
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
Some helper methods that we have:
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
assert( true, "I'll pass." );
|
|
|
|
assert( "truey", "So will I." );
|
|
|
|
assert( false, "I'll fail." );
|
|
|
|
assert( null, "So will I." );
|
|
|
|
log( "Just a simple log", "of", "values.", true );
|
|
|
|
error( "I'm an error!" );
|
|
|
|
|
|
|
|
Defining Functions
|
|
|
|
:
|
|
|
|
|
|
|
|
What ways can we define functions?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
function isNimble(){ return true; }
|
|
|
|
var canFly = function(){ return true; };
|
|
|
|
window.isDeadly = function(){ return true; };
|
|
|
|
log(isNimble, canFly, isDeadly);
|
|
|
|
|
|
|
|
Does the order of function definition matter?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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." );
|
|
|
|
log( ninja );
|
|
|
|
|
|
|
|
We can even do it if we're 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." );
|
|
|
|
|
|
|
|
But 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 = {};
|
|
|
|
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." );
|
|
|
|
|
|
|
|
What if we don't want to give the function a name?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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." );
|
|
|
|
|
|
|
|
How similar are functions and objects?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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?
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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." );
|
|
|
|
|
|
|
|
What happens when we forget to use the new operator? (cont.)
|
|
|
|
:
|
|
|
|
|
|
|
|
|
|
|
|
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){
|
|
|
|
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." );
|
|
|
|
|
|
|
|
QUIZ: 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" );
|
|
|
|
|
|
|
|
[1]: http://jsninja.com/
|
|
|
|
[2]: http://ejohn.org/
|
|
|
|
|