JavaScript学习笔记(一)//更新中

JavaScript学习笔记 - 题目练习

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

判断值是否为真值有以下两个断言测试函数

  1. assert(value[, message])
    这个测试函数在 【Boolean(value)】 为 【true】时通过断言测试,否则抛出 【AssertionError】。( value 为false,则抛出一个带有 message 属性的 【AssertionError】,其中 message 属性的值等于传入的 message 参数的值。 如果 message 参数为 undefined,则赋予默认的错误信息。)
  2. 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

Reference.

#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.

猜你喜欢

转载自blog.csdn.net/sinat_41918479/article/details/83829829