JavaScript学习笔记 - 题目练习
- Learning Advanced JavaScript
- #2: Goal: To be able to understand this function:
- #3: Some helper methods that we have:
- #5: What ways can we define functions?
- #6: Does the order of function definition matter?
- #7: Where can assignments be accessed?
- #8: Can functions be defined below return statements?
- #10: We can refer to a function, within itself, by its name.
- #11: What is the name of a function?
- #12: We can even do it if we're an anonymous function that's an object property.
- #13: But what happens when we remove the original object?
- #14: Let's give the anonymous function a name!
- #15: What if we don't want to give the function a name?
- #17: How similar are functions and objects?
- #18: How similar are functions and objects?
- #19: Is it possible to cache the return results from a function?
- #20: QUIZ: Can you cache the results of this function?
- #21: One possible way to cache the results:
- #23: What happens if a function is an object property?
- #24: What exactly does context represent?
- #25: How can we change the context of a function?
- #26: Different ways of changing the context:
- #27: QUIZ: How can we implement looping with a callback?
- #28: A possible solution for function looping:
- #30: What does the new operator do?
- #31: We have a 'this' context that is a Ninja object.
- #32: QUIZ: Add a method that gives a name to the ninja.
- #33: Add a new property and method to the object.
- #34: What happens when we forget to use the new operator?
- #35: What happens when we forget to use the new operator? (cont.)
- #36: We need to make sure that the new operator is always used.
- #37: QUIZ: Is there another, more generic, way of doing this?
- #38: A solution using arguments.callee.
- #40: Using a variable number of arguments to our advantage.
- #41: How can we find the Min/Max number in an array?
- #42: Another possible solution:
- #43: Uh oh, what's going wrong here?
- #44: QUIZ: We must convert array-like objects into actual arrays. Can any built-in methods help?
- #45: We can use built-in methods to our advantage.
- #46: QUIZ: Implement a multiplication function (first argument by largest number).
- #47: We can use call and apply to build a solution.
- #49: A basic closure.
- #50: But why doesn't this work?
- #51: Closures are frequently used for callbacks.
- #52: They're also useful for timers.
- #53: and they're also frequently used when attaching event listeners.
- #54: Private properties, using closures.
- #55: QUIZ: What are the values of the variables?
- #56: The last one is quite tricky, we'll revisit it.
- #58: Self-executing, temporary, function
- #59: Now we can handle closures and looping.
- #60: The anonymous wrapper functions are also useful for wrapping libraries.
- #61: Another way to wrap a library:
- #62: QUIZ: Fix the broken closures in this loop!
- #63: A quick wrapper function will do the trick.
- #65: Adding a prototyped method to a function.
- #66: Properties added in the constructor (or later) override prototyped properties.
- #67: Prototyped properties affect all objects of the same constructor, simultaneously, even if they already exist.
- #68: QUIZ: Make a chainable Ninja method.
- #69: The chainable method must return this.
- #71: Examining the basics of an object.
- #72: We can still use the constructor to build other instances.
- #73: QUIZ: Make another instance of a Ninja.
- #74: QUIZ: Use the .constructor property to dig in.
- #76: The basics of how prototypal inheritance works.
- #77: QUIZ: Let's try our hand at inheritance.
- #78: The result is rather straight-forward.
- #80: We can also modify built-in object prototypes.
- #81: Beware: Extending prototypes can be dangerous.
- #83: What happens when we try to bind an object's method to a click handler?
- #84: We need to keep its context as the original object.
- #85: Add a method to all functions to allow context enforcement.
- #86: Our final target (the .bind method from Prototype.js).
- #88: How does a function's length property work?
- #89: We can use it to implement method overloading.
- #90: How method overloading might work, using the function length property.
Learning Advanced JavaScript
Reference: Learning Advanced JavaScript
Courses: Web2.0 Programming.
Tools: Repl.it
#2: 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)));
};
};
上述代码中,
prototype是函数的一个属性,并且是函数的原型对象。引用它的必然是函数。call是函数的一个方法,关于这个方法,它也是只有函数才能够调用的,它的作用是:调用引用它的函数。slice(start[,end])是js的一个原生数组函数,作用是获取数组中从start下标开始到end下标结束的元素。 arguments是js函数对象的一个属性,作用是获取函数的实参,返回的是一个以函数实参为属性元素的对象。而Array是js中生成数组的关键字,数组的关键字Array不能这样子Array.xx直接调用js的数组函数,而是要用Array.prototype来调用数组函数。(给原型对象增加属性,也就是给对象增加公用的属性)Array.prototype是一个数组,String.prototype是一个字符串,Object.prototype是一个对象。
reference.
args=Array.prototype.slice.call(arguments)将调用bind函数时参数集合arguments转换为数组array。
object=args.shift()将args数组第一个元素取出作为当前对象
匿名函数中,调用args.concat(Array.prototype.slice.call(arguments))是为了将调用匿名函数时传入的参数与调用bind时参数合并成一个参数数组
以一个调用示例来看上述过程:
var obj = { x: 'prop x' };
//args = Array.prototype.slice.call(arguments)后args = [obj, 12, 23 ]
//object=args.shift()后,args =[12, 23] ,object =obj
var boundExample = example.bind(obj, 12, 23);
boundExample(36, 49); // arguments => 36, 49 ,调用args.concat(Array.prototype.slice.call(arguments))后,arguments that our example() function receives => [12, 23, 36, 49]
参考链接
#3: Some helper methods that we have:
assert( true, "I'll pass." ); //undefined
assert( "truey", "So will I." ); //undefined
assert( false, "I'll fail." ); //{ [AssertionError [ERR_ASSERTION]: I'll fail.]
//generatedMessage: false,
//name: 'AssertionError [ERR_ASSERTION]',
//code: 'ERR_ASSERTION',
//actual: false,
//expected: true,
//operator: '==' }
assert( null, "So will I." ); //{ [AssertionError [ERR_ASSERTION]: So will I.]
//generatedMessage: false,
//name: 'AssertionError [ERR_ASSERTION]',
//code: 'ERR_ASSERTION',
//actual: null,
//expected: true,
//operator: '==' }
log( "Just a simple log", "of", "values.", true ); //ReferenceError: log is not defined
error( "I'm an error!" ); //ReferenceError: error is not defined
// Terminal node v10.13.0
判断值是否为真值有以下两个断言测试函数
- assert(value[, message])
这个测试函数在 【Boolean(value)】 为 【true】时通过断言测试,否则抛出 【AssertionError】。( value 为false,则抛出一个带有 message 属性的 【AssertionError】,其中 message 属性的值等于传入的 message 参数的值。 如果 message 参数为 undefined,则赋予默认的错误信息。) - assert.ok(value[, message])
assert.ok() 与 assert()的作用是一样的,都是测试【value】是否为真值。而且用法也一样,所以可以将assert()视为assert.ok()的语法糖
参考链接
#5: What ways can we define functions?
function isNimble(){ return true; }
var canFly = function(){ return true; };
window.isDeadly = function(){ return true; };
log(isNimble, canFly, isDeadly);
#6: 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; }
函数可定义在任何地方,次序不重要,javascript在执行之前会将所有的变量和函数进行升级(define promotion)。
#7: Where can assignments be accessed?
assert( typeof canFly == "undefined", "canFly doesn't get that benefit." ); //[AssertionError [ERR_ASSERTION]: canFly doesn't get that benefit.]
assert( typeof isDeadly == "undefined", "Nor does isDeadly." );
var canFly = function(){ return true; }; //undefined
window.isDeadly = function(){ return true; };
如果是赋值方式定义函数指针的话,必须得在调用该函数指针之前定义,否则将无法访问。在javascript中函数的定义和变量的定义都为提升到global域当中,但变量只有定义会提升,其值并不提升,因此你会看到undefined。赋值的命名函数其函数名只在函数内部可以识别出.。
提升(Hoisting)是 JavaScript 默认将当前作用域提升到前面去的的行为。
提升(Hoisting)应用在变量的声明与函数的声明。
使用表达式定义函数时无法提升。
eg. var x = function (a, b) {return a * b}; //表达式声明
JavaScript 函数可以通过一个表达式定义。函数表达式可以存储在变量中;在函数表达式存储在变量后,变量也可作为一个函数使用;实际上是一个匿名函数 (函数没有名称)。函数存储在变量中,不需要函数名称,通常通过变量名来调用。
#8: 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; }
} //undefined
stealthCheck();//true
stealthCheck函数在执行之前会对所有内部的函数和变量进行升级。
#10: We can refer to a function, within itself, by its name.
function yell(n){
return n > 0 ? yell(n-1) + "a" : "hiy";
} //yell(4)=='hiyaaaa'
assert( yell(4) == "hiyaaaa", "Calling the function by itself comes naturally." );
#11: What is the name of a function?
var ninja = function myNinja(){
assert( ninja == myNinja, "This function is named two things - at once!" );
}; //undefined
ninja(); //true
//myNinja(); //ReferenceError: myNinja is not defined
assert( typeof myNinja == "undefined", "But myNinja isn't defined outside of the function." ); //true
log( ninja );//console.log(typeof(ninja)): functioin
//typeof myNinja == 'undefined'
可以将一个匿名函数做为一个对象的属性
以var fn= function doSth(){}这种形式定义的函数,在全局中只能以fn为名来调用。
#12: 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";
}
}; //ninja.yell(4) == 'hiyaaaa'
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." );
可以将一个对象的属性继承给另一个对象。
#13: But what happens when we remove the original object?
#13: But what happens when we remove the original object?
var ninja = {
yell: function(n){
return n > 0 ? ninja.yell(n-1) + "a" : "hiy";
}
}; //ninja.yell(4) == 'hiyaaaa'
assert( ninja.yell(4) == "hiyaaaa", "A single object isn't too bad, either." );
var samurai = { yell: ninja.yell };
var ninja = null; //!! compared with #14
try {
samurai.yell(4);
} catch(e){
assert( false, "Uh, this isn't good! Where'd ninja.yell go?" ); //AssertionError [ERR_ASSERTION]
}
如果原始对象为空,则不能发生继承。
匿名函数作为对属性时,不会被声明,只在被调用时执行。因而当对应属性被删除时,再调用该函数则无效。
#14: Let’s give the anonymous function a name!
#14: 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 = {}; //!!!!! not null
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." ); //samurai.yell(4) == 'hiyaaaa'
当属性中的函数具名时,在属性被创建并赋值时,便声明了以属性键名为名的函数。及时属性之后被清空,函数依然存在。
#15: 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";
}
}; //ninja.yell(4) == 'hiyaaaa'
assert( ninja.yell(4) == "hiyaaaa", "arguments.callee is the function itself." );
arguments和数组类似但不是数组、它保存着函数调用时传递过来的所有参数、下标从0开始顺序接收。arguments对象不是一个Array、它类似于Array,但不具有除了length方法的任何方法,例如 sort方法、pop方法等 ,但它可以被转换为一个真正的Array。
callee是arguments对象的一个属性、用来指向当前执行的函数。
在匿名函数中,可以用arguments.callee调用函数自身
#17: How similar are functions and objects?
var obj = {};
var fn = function(){};
assert( obj && fn, "Both the object and function exist." );
Function
函数就是对象,代表函数的对象就是函数对象。所有的函数对象是被 Function 这个函数对象构造出来的。也就是说,Function 是最顶层的构造器。它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。
Object
Object 是最顶层的对象,所有的对象都将继承 Object 的原型,你也要知道 Object 也是一个函数对象,所以说 Object 是被 Function 构造出来的。
Function 与 Object 关系图:
var Foo= function(){}
var f1 = new Foo();
console.log(f1.__proto__ === Foo.prototype); //true
console.log(Foo.prototype.constructor === Foo); //true
var o1 =new Object();
console.log(o1.__proto__ === Object.prototype); //true
console.log(Object.prototype.constructor === Object); //true
console.log(Foo.prototype.__proto__ === Object.prototype); //true
//Function and Object
console.log(Function.__proto__ === Function.prototype); //true
console.log(Object.__proto__ === Function.prototype); //true
console.log(Object.prototype.__proto__); //null
console.log(Object.__proto__ === Function.prototype); //true
#18: How similar are functions and objects?
var obj = {};
var fn = function(){};
obj.prop = "some value";
fn.prop = "some value"; //obj.prop == fn.prop, true
assert( obj.prop == fn.prop, "Both are objects, both have the property." );
#19: 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 );
//Elements found: 0
//Cache found: 0
#20: 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?" );
//isPrime(5) true;
//isPrime.cache[5] TypeError: Cannot read property '5' of undefined
#21: One possible way to cache the results:
function isPrime( num ) {
if ( isPrime.cache[ num ] != null )
return isPrime.cache[ num ];
//If the parm cached,return corresponding result
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 //cached the parm&result
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." );
//isPrime(5) true;
//isPrime.cache[5] true
函数作为一个对象,可以在其子对象中进行数据缓存。
#23: What happens if a function is an object property?
#23: 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." );
#24: What exactly does context represent?
#24: 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." );
#25: How can we change the context of a function?
#25: 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." );
#26: Different ways of changing the context:
#26: 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" );
#27: QUIZ: How can we implement looping with a callback?
#27: 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.");
});
#28: A possible solution for function looping:
#28: 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.");
});
#30: What does the new operator do?
#30: 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." );
#31: We have a ‘this’ context that is a Ninja object.
#31: 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." );
#32: QUIZ: Add a method that gives a name to the ninja.
#32: 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." );
#33: Add a new property and method to the object.
#33: 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." );
#34: What happens when we forget to use the new operator?
#34: 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." );
#35: What happens when we forget to use the new operator? (cont.)
#35: 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." );
#36: We need to make sure that the new operator is always used.
#36: 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." );
#37: QUIZ: Is there another, more generic, way of doing this?
#37: 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." );
#38: A solution using arguments.callee.
#38: 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." );
#40: Using a variable number of arguments to our advantage.
#40: 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." );
#41: How can we find the Min/Max number in an array?
#41: 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.");
#42: Another possible solution:
#42: 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.");
#43: Uh oh, what’s going wrong here?
#43: 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.");
#44: QUIZ: We must convert array-like objects into actual arrays. Can any built-in methods help?
#44: 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.");
#45: We can use built-in methods to our advantage.
#45: 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.");
#46: QUIZ: Implement a multiplication function (first argument by largest number).
#46: 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.)" );
#47: We can use call and apply to build a solution.
#47: 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.)" );
#49: A basic closure.
#49: A basic closure.
var num = 10;
function addNum(myNum){
return num + myNum;
}
assert( addNum(5) == 15, "Add two numbers together, one from a closure." );
#50: But why doesn’t this work?
#50: 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." );
#51: Closures are frequently used for callbacks.
#51: 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." );
});
#52: They’re also useful for timers.
#52: 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);
#53: and they’re also frequently used when attaching event listeners.
#53: 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." );
#54: Private properties, using closures.
#54: 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." );
#55: QUIZ: What are the values of the variables?
#55: 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);
}
#56: The last one is quite tricky, we’ll revisit it.
#56: 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);
}
#58: Self-executing, temporary, function
#58: 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" );
#59: Now we can handle closures and looping.
#59: 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);
#60: The anonymous wrapper functions are also useful for wrapping libraries.
#60: The anonymous wrapper functions are also useful for wrapping libraries.
(function(){
var myLib = window.myLib = function(){
// Initialize
};
// ...
})();
#61: Another way to wrap a library:
#61: Another way to wrap a library:
var myLib = (function(){
function myLib(){
// Initialize
}
// ...
return myLib;
})();
#62: QUIZ: Fix the broken closures in this loop!
#62: 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);
}
#63: A quick wrapper function will do the trick.
#63: 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);
#65: Adding a prototyped method to a function.
#65: 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." );
#66: Properties added in the constructor (or later) override prototyped properties.
#66: 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." );
#67: Prototyped properties affect all objects of the same constructor, simultaneously, even if they already exist.
#67: 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." );
#68: QUIZ: Make a chainable Ninja method.
#68: 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." );
#69: 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." );
#71: 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." );
#72: 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." );
#73: 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." );
#74: 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." );
#76: 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" );
#77: 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." );
#78: 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." );
#80: 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) );
});
#81: 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;
#83: 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" );
#84: 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" );
#85: 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" );
#86: 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" );
#88: 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" );
#89: 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 );
};
}
#90: 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" );
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" );
This tutorial contains code and discussion from the upcoming book Secrets of the JavaScript Ninja by John Resig.