1.函数调用、方法调用以及构造函数调用只是单个构造对象的三种不同的使用模式。
第一种函数调用模式:
function hello(username){ return ‘hello,’+ username; } hello(‘Keyser Soze’);
第二种模式是方法调用:
var obj = { hello:function(){ return ‘hello,’+ this.username; }, username : ‘Hans Gruber’; }; obj.hello();
通过某个对象调用方法将查找该方法并将该对象作为该方法的接受者。一个非方法的函数调用会将全局对象作为接受者。
第三种模式是构造函数:就像方法和纯函数一样,构造函数也是由function运算符定义的。
function User(name,passwordHash){ this.name = name ; this.passwordHash = passwordHash; }
使用new操作符来调用User则视其为构造函数:
var u = new User(‘sfalken’, ‘0ef33ae791068ec64b502d6cb0191387’ ); u.name; //‘sfalken’
构造函数调用将一个全新的对象作为this变量的值,并隐式返回这个新对象作为调用结果。构造函数的主要职责是初始化该对象。
2.将函数作为参数或返回值的函数称为高阶函数:
假设有一个简单的转换字符串数组的操作,我们可以使用循环数组实现:
var names = [‘Fred’,‘Wilma’,‘Pebbles’]; var upper = []; for(var i = 0, n = names.length; i < n ; i++){ upper[i] = names[i].toUpperCase(); } upper;
使用数组便利的map方法,我们可以完全消除循环,仅仅使用一个局部函数就可以实现对元素的逐个转换。
var names = [‘Fred’,‘Wilma’,‘Pebbles’]; var upper = names.map(function(name){ return name.toUpperCase(); }) upper; //[‘FRED’,‘WILMA’,‘PEBBLES’];
需要引入高阶函数抽象的信号是出现重复或相似的代码。
3.使用call方法自定义接受者来调用方法
使用call方法的三种情况:
①通常情况下,函数或方法的接受者是由调用者的语法决定的。有时需要自定义接受者来调用函数,因为该函数可能并不是期望的接受者对象的属性。当然可以将方法作为一个新的属性添加到接受者对象中。但这种方式不仅让人感觉别扭而且相当危险。幸运的是,函数对象具有一个内置的方法call来自定义接收者,可以通过函数对象的call方法来调用其自身:
f.call(obj,arg1,arg2,arg3);
此行为与直接调用函数自身很类似:
f(arg1,arg2,arg3);
②当调用的方法已经被删除、修改或者覆盖时,call方法就派上用场了。使用hasOwnProperty方法的call方法使调用字典对象中的方法成为可能,即使hasOwnProperty方法并没有存储在该对象中。
var hasOwnProperty = { }.hasOwnProperty ; dict.foo = 1 ; delete dict.hasOwnProperty ; hasOwnProperty.call(dict , ‘foo’); hasOwnProperty.call(dict ,‘hasOwnProperty’);
③定义高阶函数时,call方法也特别实用。高阶函数的一个惯用方法是接收一个可选的参数作为调用该函数的接受者。例如,表示键值对列表的对象可能提供名为forEach的方法。
var table = { entries : []; addEntry : function(key , value){ this.entries.push({ key : key , value : value }); }, forEach : function(f, thisArg){ var entries = this.entries; for(var i = 0 , n = entries.length ; i < n; i ++){ var entry = entries[i]; f.call(thisArg , entry.key , entry.value , i ); } } } ;
上述例子允许table对象的使用者将一个方法作为table.forEach的回调函数f,并为该方法提供一个合理的接收者。例如,可以方便的将一个table的内容复制到另一个中。
table1.forEach(table2.addEntry , table2) ;
4.使用apply方法通过不懂数量的参数调用函数
Apply方法需要一个参数数组,然后将数组的每一个元素作为调用的单独参数调用该函数。除了参数数组,apply方法指定第一个参数绑定到被调用函数的this变量。由于average函数没有引用this变量,因此,我们可以简单地传递null。
var scores = getAllScores(); average.apply(null,scores);
例如,如果scores有三个元素,那么以上代码的行为与average(scores[0],scores[1],scores[2])一致。
apply方法也可用于可变参数方法。例如,buffer对象包含一个可变参数的append方法,该方法添加元素到函数内部的state数组中。
var buffer = { state : [ ] ; append : function(){ for(var i = 0 , n = arguments.length ; i < n ; i ++){ this.state.push(arguments[i]); } } };
5.使用argumen创建可变参数的函数
固定元数版本的averageOfArray函数是很容易实现的:
function averageOfArray(a){ for(var i = 0 , sum = 0, n = a.length ; i < n ; i ++){ sum += a[i] ; } return sum / n ; } averageOfArray([2,7,1,8,2,8,1,8]);
提供一个可变参数的函数,委托给固定元数版本来实现可变参数的函数:
function average(){ return averageOfArray(arguments) ; }
这样一来,函数的使用者就无需借助apply方法,因为apply方法会降低可读性而且经常导致性能损失。
6.永远不要修改arguments对象
arguments对象可能看起来像一个数组,但它不总是表现的像数组。使用[].slice.call(arguments)将arguments对象复制到一个真正的数组中再进行修改。
7.使用变量保存arguments的引用
引用arguments时当心函数嵌套层级。绑定一个明确作用域的引用到arguments变量,从而可以在嵌套的函数中引用它。
function values(){ var i = 0 , n = arguments.length , a = arguments ; return { hasNext : function(){ return i < n ; }, next : function(){ if(i >=n){ throw new Error(“end of iteration”) ; } return a[i++] ; } }; } var it = values(1,4,1,4,2,1,3,5,6); it.next(); it.next(); it.next();
8.使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,这些代码可以随后被执行,可是应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数,字符串表示代码不够灵活的一个重要原因是:它们不是闭包。
假设有一个简单的多次重复用户提供的动作的函数。
function repeat(n , action){ for(var i = 0 ; i < n ; i ++){ eval(action) ; } }
该函数在全局作用域会工作的很好,因为eval函数会将出现在字符串中的所有变量引用作为全局变量来解释。
9.避免使用非标准的栈检查属性
调用栈是指当前正在执行的活动函数链。在某些旧的宿主环境中,每个arguments对象都含有两个额外的属性:arguments.callee和arguments.caller。前者指向使用该arguments对象被调用的函数;后者指向调用该函数arguments对象的函数。许多环境仍然支持arguments.callee,但它除了允许匿名函数递归地引用其自身之外,就没有更多的用途了。
避免使用非标准的arguments.callee和arguments.caller属性,因为它们不具备良好的移植性。避免使用非标准的函数对象caller属性,因为在包含全栈信息方面,它是不可靠的。