在JavaScript中该互,函數(shù)即對(duì)象欲低,程序可以隨意操控它們辕宏。比如,JavaScript可以把函數(shù)賦值給變量砾莱,或者作為參數(shù)傳遞給其他函數(shù)瑞筐,并且可以給它們?cè)O(shè)置屬性,甚至調(diào)用它們的方法腊瑟。
函數(shù)定義
一般函數(shù)定義的形式有以下幾種:
- 函數(shù)聲明法
function factorial(x) {
if(x <= 1) return 1;
return x * factorial(x - 1);
}
注意:函數(shù)聲明語(yǔ)句"被提前"到腳本的頂部聚假,所以可以在定義之前的代碼調(diào)用函數(shù)块蚌。
- 函數(shù)賦值法(可定義為匿名函數(shù))
var square = function(x) { return x*x; }
注意:變量的聲明是可以提前的,但是給變量的賦值不會(huì)提前膘格,所以這種方式的函數(shù)在定義之前無(wú)法調(diào)用峭范。
- 函數(shù)定義后立即執(zhí)行
var tensquared = (function(x) { return x*x; }(10));
注意: function左邊的左括號(hào)是必需的,因?yàn)槿绻粚?xiě)左括號(hào)瘪贱,JavaScript解釋器會(huì)將關(guān)鍵字function解析為函數(shù)聲明語(yǔ)句纱控。使用左括號(hào)后,JavaScript解釋器才會(huì)正確地將其解析為函數(shù)定義表達(dá)式菜秦。
- 嵌套函數(shù)
function hypotenuse(a, b) {
function square(x) { return x*x; }
return Math.sqrt( square(a) + square(b) );
}
注意:嵌套函數(shù)中甜害,注意作用域的使用。
函數(shù)調(diào)用
有4種方式可以調(diào)用JavaScript函數(shù):
- 作為函數(shù)
- 作為方法
- 作為構(gòu)造函數(shù)
- 通過(guò)call()和apply()方法間接調(diào)用
作為函數(shù)調(diào)用
函數(shù)調(diào)用就是直接通過(guò)function對(duì)象來(lái)調(diào)用函數(shù)球昨。
var probability = factorial(5) / factorial(13);
根據(jù)ECMAScript3和非嚴(yán)格的ECMAScript5的規(guī)定尔店,函數(shù)調(diào)用的上下文(this的值)是全局對(duì)象(window)。在ECMAScript 5的嚴(yán)格模式下主慰,調(diào)用上下文是undefined嚣州。
作為方法調(diào)用
- 方法調(diào)用是通過(guò)對(duì)象的屬性來(lái)調(diào)用函數(shù)。
- 方法調(diào)用和函數(shù)調(diào)用最大的一個(gè)區(qū)別是:調(diào)用上下文河哑。方法調(diào)用的上下文是調(diào)用它的對(duì)象避诽,可以通過(guò)this關(guān)鍵字引用該對(duì)象。
- this是一個(gè)關(guān)鍵字璃谨,不是變量沙庐,也不是屬性名。JavaScript不允許給this賦值佳吞。
var calculator = {
operand1: 1,
operand2: 2,
add: function() {
this.result = this.operand1 + this.operand2;
}
};
calculator.add(); // 方法調(diào)用
calculator["add"](); // 另一種形式的方法調(diào)用
calculator.result; // 2拱雏,對(duì)象屬性添加成功
- 如果嵌套函數(shù)作為函數(shù)調(diào)用,則this值不是全局對(duì)象就是undefined底扳。
var o = {
m: function() {
var self = this;
console.log(this === o); // true
f(); // 作為函數(shù)調(diào)用
function f() {
console.log(this === o); // false
console.log(this === window); // true
}
}
}
- 如果嵌套函數(shù)作為方法調(diào)用铸抑,則this值指向調(diào)用它的對(duì)象。
var o = {};
function outer() {
var inner = function() {
console.log(this === o);
};
o.m = inner;
}
outer();
o.m(); // 輸出: true衷模,作為方法調(diào)用
作為構(gòu)造函數(shù)調(diào)用
- 如果函數(shù)或方法調(diào)用之前帶有關(guān)鍵字new鹊汛,它就構(gòu)成構(gòu)造函數(shù)調(diào)用。
- 構(gòu)造函數(shù)調(diào)用和普通的函數(shù)調(diào)用以及方法調(diào)用在實(shí)參處理阱冶、調(diào)用上下文和返回值方面都有不同刁憋。
- 構(gòu)造函數(shù)調(diào)用會(huì)創(chuàng)建一個(gè)新的空對(duì)象,這個(gè)對(duì)象繼承自構(gòu)造函數(shù)的prototype屬性木蹬。構(gòu)造函數(shù)使用這個(gè)新創(chuàng)建的對(duì)象作為調(diào)用上下文至耻,并可以使用this關(guān)鍵字引用這個(gè)新創(chuàng)建的對(duì)象。
var o = {
m: function() {
console.log(this === o);
}
};
o.m(); // true,方法調(diào)用
var s = new o.m(); // false尘颓,構(gòu)造函數(shù)調(diào)用
間接調(diào)用
- JavaScript中的函數(shù)也是對(duì)象走触,函數(shù)對(duì)象也可以包含方法。其中的2個(gè)方法call()和apply()可以用來(lái)間接地調(diào)用函數(shù)疤苹。
- 兩個(gè)方法允許顯式指定調(diào)用所需的this值互广,也就是說(shuō),任何函數(shù)可以作為任何對(duì)象的方法來(lái)調(diào)用痰催,哪怕這個(gè)函數(shù)不是那個(gè)對(duì)象的方法兜辞。
- 在ECMAScript3和非嚴(yán)格模式中,傳入的null和undefined都會(huì)被全局對(duì)象代替夸溶,而其他原始值則會(huì)被相應(yīng)的包裝對(duì)象所替代逸吵。
f.call(o);
f.apply(o);
函數(shù)的實(shí)參和形參
JavaScript中的函數(shù)定義并未指定函數(shù)形參的類型,函數(shù)調(diào)用也未對(duì)傳入的實(shí)參值做任何類型檢查缝裁。實(shí)際上扫皱,JavaScript函數(shù)調(diào)用甚至不檢查傳入形參的個(gè)數(shù)。函數(shù)對(duì)于未傳入的參數(shù)賦值為undefined捷绑,對(duì)于多余的參數(shù)忽略韩脑。
可選形參
由于調(diào)用函數(shù)傳入的實(shí)參個(gè)數(shù)可以比形參個(gè)數(shù)少,所以函數(shù)應(yīng)當(dāng)對(duì)此有一個(gè)較好的適應(yīng)性粹污,給省略的參數(shù)賦一個(gè)合理的默認(rèn)值段多。
function getPropertyNames(o, /* optional */ a) {
if (a === undefined) a = []; // 如果未定義,則使用新數(shù)組
for (var property in o)
a.push(property);
return a;
}
var a = getPropertyNames(o); // 將o的屬性存儲(chǔ)到一個(gè)新數(shù)組中
getPropertyNames(p, a); // 將p的屬性追加到數(shù)組a中
其中第一個(gè)if判斷語(yǔ)句可簡(jiǎn)寫(xiě)為:
a = a || [ ];
實(shí)參對(duì)象(arguments)
- 實(shí)參對(duì)象是一個(gè)類數(shù)組對(duì)象壮吩,可以通過(guò)數(shù)字下標(biāo)訪問(wèn)傳入函數(shù)的實(shí)參值进苍,而不用非要通過(guò)形參名字來(lái)得到實(shí)參。
下面的函數(shù)可以接收任意數(shù)量的實(shí)參:
function max(/* ... */) {
var max = Number.NEGATIVE_INFINITY;
// 遍歷實(shí)參鸭叙,查找并記住最大值
for(var i = 0; i < arguments.length; i++)
if(arguments[i] > max) max = arguments[i];
return max;
}
- 在非嚴(yán)格模式下觉啊,實(shí)參對(duì)象的數(shù)組元素是函數(shù)形參所對(duì)應(yīng)實(shí)參的別名,可以通過(guò)實(shí)參對(duì)象修改實(shí)參的值沈贝。
- 在非嚴(yán)格模式下杠人,arguments僅僅是一個(gè)標(biāo)識(shí)符,在嚴(yán)格模式中宋下,它變成了一個(gè)保留字嗡善。嚴(yán)格模式中的函數(shù)無(wú)法使用arguments作為形參名或局部變量名,也不能給arguments賦值学歧。
function f(x) {
console.log(x); // 輸出實(shí)參的初始值
arguments[0] = null; // 修改實(shí)參數(shù)組元素同樣會(huì)修改x的值
console.log(x); // 輸出"null"
}
callee和caller屬性
- 除了數(shù)組元素罩引,實(shí)參對(duì)象還定義了callee和caller屬性。
- callee屬性指代當(dāng)前正在執(zhí)行的函數(shù)撩满。caller指代調(diào)用當(dāng)前正在執(zhí)行的函數(shù)的函數(shù)。
在匿名函數(shù)中,可通過(guò)callee來(lái)遞歸地調(diào)用自身:
var factorial = function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x-1);
}
將對(duì)象屬性用做實(shí)參
當(dāng)一個(gè)函數(shù)包含超過(guò)3個(gè)形參時(shí)伺帘,對(duì)于程序員說(shuō)昭躺,要記住調(diào)用函數(shù)中實(shí)參的正確順序比較困難。最好通過(guò)名/值對(duì)的形式來(lái)傳入?yún)?shù)伪嫁,這樣參數(shù)的順序就無(wú)關(guān)緊要了领炫。
function arraycopy(/* array */ from, /* array */ to, /* integer */ length) {
// 邏輯代碼
}
// 使用對(duì)象當(dāng)參數(shù)
function easycopy(args) {
arraycopy(args.from, args.to, args.length);
}
var a = [1,2,3,4], b = [];
easycopy({ from: a, to: b, length: 4});
實(shí)參類型校驗(yàn)
JavaScript會(huì)在必要的時(shí)候進(jìn)行類型轉(zhuǎn)換,如果期望的實(shí)參是一個(gè)字符串张咳,那么實(shí)參值無(wú)論是原始值還是對(duì)象都可以很容易地轉(zhuǎn)換成字符串帝洪。但是當(dāng)期望的實(shí)參是一個(gè)數(shù)組時(shí),就無(wú)法對(duì)非數(shù)組對(duì)象進(jìn)行轉(zhuǎn)換了脚猾,所以有必要對(duì)實(shí)參進(jìn)行校驗(yàn)葱峡。
function flexisum(a) {
var total = 0;
for (var i=0; i < arguments.length; i++) {
var element = arguments[i], n;
if (element == null) continue; // 忽略null和undefined實(shí)參
if (isArray(element))
n = flexisum.apply(this, element); // 如果是數(shù)組,遞歸計(jì)算累加和
else if (typeof element === "function")
n = Number(element()); // 如果是函數(shù)龙助,調(diào)用它并做類型轉(zhuǎn)換
else
n = Number(element);
if (isNaN(n))
throw Error("flexisum(): can't convert" + element + " to number.");
total += n;
}
return total;
}
作為值的函數(shù)
在JavaScript中砰奕,函數(shù)不僅是一種語(yǔ)法,也是值提鸟,也就是說(shuō)军援,可以將函數(shù)賦值給變量,存儲(chǔ)在對(duì)象的屬性或數(shù)組的元素中称勋,作為參數(shù)傳入另外一個(gè)函數(shù)等胸哥。
// 聲明一個(gè)函數(shù)
function square(x) { return x*x; }
var s = square; // 賦值給變量
s(4); // => 16
var o = { m: square }; // 賦值給對(duì)象
o.m(5); // => 25
var a = [square, 6]; // 賦值給數(shù)組
a[0](a[1]); // => 36
自定義函數(shù)的屬性
函數(shù)是一種特殊的對(duì)象,所以可以為函數(shù)定義屬性以完成特殊的需求赡鲜。
可以通過(guò)函數(shù)屬性實(shí)現(xiàn)"靜態(tài)"變量的需求空厌。
// 由于函數(shù)聲明被提前了,因此可以在聲明之前賦值
uniqueInteger.counter = 0;
function uniqueInteger() {
return uniqueInteger.counter++;
}
var a = uniqueInteger(); // 0
a = uniqueInteger(); // 1
a = uniqueInteger(); // 2
作為命名空間的函數(shù)
不在任何函數(shù)內(nèi)聲明的變量是全局變量蝗蛙,在整個(gè)JavaScript程序中都是可見(jiàn)的蝇庭。基于這個(gè)原因捡硅,我們常常簡(jiǎn)單地定義一個(gè)函數(shù)用做臨時(shí)的命名空間哮内,在這個(gè)命名空間內(nèi)定義的變量都不會(huì)污染到全局命名空間。
function mymodule() {
// 這個(gè)模塊所使用的所有變量都是局部變量
// 而不會(huì)污染全局命名空間
}
閉包
JavaScript也采用詞法作用域壮韭,也就是說(shuō)北发,函數(shù)的執(zhí)行依賴于變量作用域,這個(gè)作用域是在函數(shù)定義時(shí)決定的喷屋,而不是調(diào)用時(shí)決定的琳拨。為了實(shí)現(xiàn)這種詞法作用域,JavaScript函數(shù)對(duì)象的內(nèi)部狀態(tài)不僅包含函數(shù)的代碼邏輯屯曹,還包含函數(shù)定義時(shí)的作用域鏈狱庇。
函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi)惊畏,看起來(lái)是函數(shù)將變量"包裹"起來(lái)了,這種特性稱為"閉包"密任。
var scope = "global scope";
function checkscope() {
var scope = "local scope";
// 定義時(shí)使用局部變量
function f() { return scope; }
return f(); // 返回函數(shù)的調(diào)用結(jié)果
}
checkscope(); // => "local scope"
如果更改下checkscope定義颜启,將返回值更改為函數(shù)定義,如下:
var scope = "global scope";
function checkscope() {
var scope = "local scope";
// 定義時(shí)使用局部變量
function f() { return scope; }
return f; // 返回函數(shù)的定義
}
checkscope()(); // => "local scope"
雖然調(diào)用函數(shù)的作用域變了浪讳,但是函數(shù)的輸出結(jié)果依然不變缰盏,因?yàn)楹瘮?shù)保存了自己的作用域鏈。
通過(guò)閉包淹遵,可以實(shí)現(xiàn)一個(gè)更好的計(jì)數(shù)器類口猜,將變量包裹起來(lái):
function counter() {
var n = 0;
return {
count: function() { return n++; }
reset: function() { n = 0; }
};
}
var c = counter();
c.count(); // => 0
c.count(); // => 1
c.reset(); // => 0
函數(shù)屬性、方法和構(gòu)造函數(shù)
length屬性
arguments.length表示傳入函數(shù)的實(shí)參的個(gè)數(shù)透揣。函數(shù)的length屬性表示函數(shù)形參的個(gè)數(shù)济炎,這個(gè)屬性是只讀的。
可以通過(guò)這個(gè)屬性對(duì)函數(shù)的參數(shù)個(gè)數(shù)進(jìn)行校驗(yàn):
function check(args) {
var actual = args.length;
var expected = args.callee.length; // 形參個(gè)數(shù)
if (actual != expected) {
throw Error("Expected " + expected + "args; got " + actual);
}
}
prototype屬性
這個(gè)屬性指向一個(gè)對(duì)象的引用淌实,這個(gè)對(duì)象稱做"原型對(duì)象"冻辩。每一個(gè)函數(shù)都包含不同的原型對(duì)象。當(dāng)將函數(shù)用做構(gòu)造函數(shù)的時(shí)候拆祈,新創(chuàng)建的對(duì)象會(huì)從原型對(duì)象上繼承屬性恨闪。
call()和apply()方法
這2個(gè)方法屬于函數(shù)的方法屬性,在前面已經(jīng)有介紹放坏,不再重復(fù)介紹咙咽。
bind()方法
bind()是在ECMAScript5中新增的方法,這個(gè)方法的主要作用就是返回一個(gè)新的函數(shù)淤年,這個(gè)函數(shù)將bind的對(duì)象作為調(diào)用上下文钧敞。
function f(y) { return this.x + y; }
var o = { x: 1 };
var g = f.bind(o); // bind返回一個(gè)函數(shù)
g(2); // =>1,以對(duì)象o作為調(diào)用上下文執(zhí)行f(y)
可以通過(guò)如下代碼來(lái)實(shí)現(xiàn)簡(jiǎn)單的bind():
function bind(f, o) {
if (f.bind) return f.bind(o);
else return function() {
// 此處的arguments為調(diào)用bind返回函數(shù)時(shí)傳遞的參數(shù)
// 上例中為2(g(2))
return f.apply(o, arguments);
}
}
但ECMAScript5中的bind()方法不僅僅是將函數(shù)綁定至一個(gè)對(duì)象麸粮,它還能將實(shí)參也綁定至this溉苛,這種編程技術(shù), 有時(shí)被稱為"柯里化"(currying)弄诲。參照下面的例子:
function f(y,z) { return this.x + y + z; };
var g = f.bind({x: 1}, 2); // 綁定this和y
g(3); // =>6愚战,this.x綁定到1,y綁定到2齐遵,z綁定到3
下面的代碼給出了更標(biāo)準(zhǔn)的bind()方法寂玲,將這個(gè)方法另存為Function.prototye.bind:
if( !Function.prototye.bind) {
Function.prototye.bind = function(o /*, args */) {
// 將this和arguments的值保存在變量中
// 以便在后面嵌套的函數(shù)中使用
var self = this, boundArgs = arguments;
// bind()返回一個(gè)函數(shù)
return function() {
// 創(chuàng)建一個(gè)實(shí)參列表,保存?zhèn)魅氲乃袑?shí)參
var args = [], i;
for(i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
for(i = 0; i < arguments.length; i++) args.push(arguments[i]);
// 以綁定對(duì)象o作為上下文來(lái)調(diào)用函數(shù)self
// 并傳遞所有的實(shí)參args
return self.apply(o, args);
};
};
}
ECMAScript5定義的bind()方法有一些特性是上述代碼無(wú)法模擬的:
- 真正的bind()方法返回的函數(shù)梗摇,length屬性是綁定函數(shù)的形參個(gè)數(shù)減去綁定的實(shí)參個(gè)數(shù)拓哟。
- 真正的bind()方法可以創(chuàng)建構(gòu)造函數(shù),如果bind()返回的函數(shù)用做構(gòu)造函數(shù)伶授,將忽略bind()傳入的this断序,但是實(shí)參會(huì)正常綁定流纹。
- 由bind()方法返回的函數(shù)并不包含prototype屬性,如果返回函數(shù)用做構(gòu)造函數(shù)违诗,則創(chuàng)建的對(duì)象從原始的未綁定構(gòu)造函數(shù)中繼承prototype捧颅,同時(shí),使用instanceof運(yùn)算符较雕,綁定構(gòu)造函數(shù)和未綁定構(gòu)造函數(shù)并無(wú)兩樣。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return this.x + ',' + this.y;
};
var p = new Point(1, 2);
p.toString(); // '1,2'
var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 以下這行代碼在 polyfill 不支持,
// 在原生的bind方法運(yùn)行沒(méi)問(wèn)題:
//(譯注:polyfill的bind方法如果加上把bind的第一個(gè)參數(shù)挚币,即新綁定的this執(zhí)行Object()來(lái)包裝為對(duì)象亮蒋,Object(null)則是{},那么也可以支持)
// var YAxisPoint = Point.bind(null, 0/*x*/);
// 綁定函數(shù)的length屬性 = 形參個(gè)數(shù) - 綁定實(shí)參個(gè)數(shù)
console.log(YAxisPoint.length); // =>1,(2-1)
// 綁定函數(shù)不包含prototype屬性
console.log(Point.prototype); // "Point { toString-function()}"
console.log(YAxisPoint.prototype); // undefined
// 綁定函數(shù)用做構(gòu)造函數(shù)
// this指代新創(chuàng)建的對(duì)象
var axisPointA = new YAxisPoint();
console.log(axisPointA.toString()); // '0,undefined'
var axisPoint = new YAxisPoint(5);
console.log(axisPoint.toString()); // '0,5'
// 使用instanceof時(shí)妆毕,綁定構(gòu)造函數(shù)和未綁定構(gòu)造函數(shù)并無(wú)兩樣
console.log(axisPoint instanceof Point); // true
console.log(axisPoint instanceof YAxisPoint); // true
console.log(new Point(17, 42) instanceof YAxisPoint); // true
toString()方法
和所有的JavaScript對(duì)象一樣慎玖,函數(shù)也有toString()方法。實(shí)際上笛粘,大多數(shù)(非全部)的toString()方法的實(shí)現(xiàn)都返回函數(shù)的完整源碼趁怔。內(nèi)置函數(shù)往往返回一個(gè)類似"[native code]"的字符串。
Function()構(gòu)造函數(shù)
- 前面已經(jīng)介紹薪前,函數(shù)可以通過(guò)定義語(yǔ)句或直接量表達(dá)式來(lái)定義润努。函數(shù)還可以通過(guò)Function()構(gòu)造函數(shù)來(lái)定義。
- Function()構(gòu)造函數(shù)可以傳入任意數(shù)量的實(shí)參示括,最后一個(gè)實(shí)參表示的是函數(shù)體铺浇。
- Function()構(gòu)造函數(shù)并不需要通過(guò)傳入實(shí)參以指定函數(shù)名。Function()會(huì)構(gòu)造一個(gè)匿名函數(shù)垛膝。
var f = new Function("x", "y", "return x*y;");
// 這個(gè)定義與下面的函數(shù)定義等價(jià)
var f = function(x, y) { return x*y; }
Function()構(gòu)造函數(shù)有以下幾個(gè)特點(diǎn):
- Function()在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建并編譯函數(shù)鳍侣。
- 每次調(diào)用Function()構(gòu)造函數(shù)都會(huì)解析函數(shù)體,并創(chuàng)建新的函數(shù)對(duì)象吼拥。
- Function()構(gòu)造函數(shù)創(chuàng)建的函數(shù)并不使用詞法作用域倚聚,函數(shù)體代碼的編譯總是在頂層函數(shù)執(zhí)行。
var scope = "global";
function constructFunction() {
var scope = "local";
return new Function("return scope"); // 無(wú)法捕獲局部作用域
}
constructFunction()(); // => "global"
可調(diào)用對(duì)象(callable object)
可調(diào)用對(duì)象是一個(gè)對(duì)象凿可,可以在函數(shù)調(diào)用表達(dá)式中調(diào)用這個(gè)對(duì)象惑折。所有的函數(shù)都是可調(diào)用的,但并非所有的可調(diào)用對(duì)象都是函數(shù)矿酵。
- IE8之前的版本實(shí)現(xiàn)了客戶端方法(諸如window.alert()和Document.getElementsById())唬复,使用了可調(diào)用的宿主對(duì)象,而不是內(nèi)置函數(shù)對(duì)象全肮。IE9將它們實(shí)現(xiàn)為真正的函數(shù)敞咧,因此此類可調(diào)用的對(duì)象越來(lái)越罕見(jiàn)。
- 另外一個(gè)常見(jiàn)的可調(diào)用對(duì)象是RegExp對(duì)象辜腺,但代碼最好不要對(duì)可調(diào)用的RegExp對(duì)象有太多依賴休建,對(duì)RegExp執(zhí)行typeof運(yùn)算的結(jié)果并不統(tǒng)一乍恐,在有些瀏覽器中返回"function",在有些返回"object"测砂。
檢測(cè)一個(gè)對(duì)象是否是真正的函數(shù)對(duì)象:
function isFunction() {
return Object.prototye.toString.call(x) === "[object Function]";
}
函數(shù)式編程
和Lisp茵烈、Haskell不同,JavaScript并非函數(shù)式編程語(yǔ)言砌些,但可以像操控對(duì)象一樣操控函數(shù)呜投,也就是說(shuō),JavaScript中可以應(yīng)用函數(shù)式編程技術(shù)存璃。
使用函數(shù)處理數(shù)組
使用函數(shù)式編程仑荐,簡(jiǎn)潔地實(shí)現(xiàn)計(jì)算平均值、標(biāo)準(zhǔn)差:
// 首先定義2個(gè)函數(shù)對(duì)象
var sum = function(x,y) { return x+y; }
var square = function(x) { return x*x; }
// 使用函數(shù)式編程計(jì)算平均數(shù)纵东、標(biāo)準(zhǔn)差
var data = [1,1,3,5,5];
// 計(jì)算平均數(shù)
var mean = data.reduce(sum) / data.length;
// 計(jì)算標(biāo)準(zhǔn)差
var deviations = data.map(function(x) { return x-mean; });
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / (data.length-1));
高階函數(shù)(higher-order function)
高階函數(shù)就是操作函數(shù)的函數(shù)粘招,它接收一個(gè)或多個(gè)函數(shù)作為參數(shù),并返回一個(gè)新函數(shù)偎球。
function mapper(f) {
return function(a) { return a.map(f); } // 注意: 此處沒(méi)有對(duì)參數(shù)a進(jìn)行數(shù)組驗(yàn)證
}
var increment = function(x) { return x+1; }
var incrementer = mapper(increment);
incrementer([1,2,3]); // => [2,3,4]
不完全函數(shù)(partial function)
不完全函數(shù)是一種函數(shù)變換技巧洒扎,即把一次完整的函數(shù)調(diào)用拆成多次函數(shù)調(diào)用,每次傳入的實(shí)參都是完整實(shí)參的一部分衰絮,每個(gè)拆分開(kāi)的函數(shù)叫做不完全函數(shù)袍冷,每次函數(shù)調(diào)用叫做不完全調(diào)用(partial application)。
// 實(shí)現(xiàn)一個(gè)工具函數(shù)猫牡,將類數(shù)組對(duì)象轉(zhuǎn)換為真正的數(shù)組
function array(a, n) { return Array.prototye.slice.call(a, n || 0); }
// 將第1次調(diào)用的實(shí)參放在左側(cè)
function partialLeft(f /* , ... */ ) {
var args = arguments;
return function() {
var a = array(args, 1); // 獲取第1個(gè)參數(shù)之后所有的實(shí)參
a = a.concat(array(arguments));
return f.apply(this, a);
};
}
// 將第1次調(diào)用的實(shí)參放在右側(cè)
function partialRight(f /* , ... */ ) {
var args = arguments;
return function() {
var a = array(arguments);
a = a.concat(array(args, 1));
return f.apply(this, a);
};
}
// 將第1次調(diào)用實(shí)參中的undefined值替換成第2次調(diào)用的實(shí)參
function partial(f /* , ... */ ) {
var args = arguments;
return function() {
var a = array(args, 1);
var i = 0, j = 0;
for(; i < a.length; i++) {
if(a[i] === undefined) a[i] = arguments[j++];
}
a = a.concat(array(arguments, j));
return f.apply(this, a);
};
}
// 這個(gè)函數(shù)帶有3個(gè)參數(shù)
var f = function(x, y, z) { return x * (y - z); };
partialLeft(f, 2)(3, 4); // => -2 [2 * (3 - 4)]
partialRight(f, 2)(3, 4); // => 6; [3 * (4 -2)]
partial(f, undefined)(3, 4); // => -6; [3 * (2 - 4)]
記憶(memorization)
記憶只是一種編程技巧难裆,本質(zhì)上是以空間換時(shí)間,在客戶端代碼中镊掖,執(zhí)行時(shí)間往往成為瓶頸乃戈,因此這種做法是非常可取的亩进。
下面定義一個(gè)高階函數(shù)症虑,接收一個(gè)函數(shù)作為實(shí)參,并返回帶有記憶能力的函數(shù):
function memorize(f) {
var cache = {};
return function() {
var key = arguments.length + Array.prototye.join.call(arguments, ",");
if(key in cache)
return cache[key];
else
return cache[key] = f.apply(this, arguments);
};
}
// 定義有記憶功能的斐波那契函數(shù)
var factorial = memorize(function(n) {
return (n <= 1) ? 1: n * factorial(n-1);
});
factorial(5); // => 120归薛,同時(shí)記憶了4~1的值