JavaScript函數(shù)

在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的值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谍憔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子主籍,更是在濱河造成了極大的恐慌习贫,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件千元,死亡現(xiàn)場(chǎng)離奇詭異苫昌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)幸海,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門祟身,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奥务,“玉大人,你說(shuō)我怎么就攤上這事袜硫÷仍幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵婉陷,是天一觀的道長(zhǎng)帚称。 經(jīng)常有香客問(wèn)我,道長(zhǎng)秽澳,這世上最難降的妖魔是什么世杀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮肝集,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛛壳。我一直安慰自己杏瞻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布衙荐。 她就那樣靜靜地躺著捞挥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忧吟。 梳的紋絲不亂的頭發(fā)上砌函,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音溜族,去河邊找鬼讹俊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛煌抒,可吹牛的內(nèi)容都是我干的仍劈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼寡壮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贩疙!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起况既,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤这溅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后棒仍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悲靴,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年莫其,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了对竣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庇楞。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖否纬,靈堂內(nèi)的尸體忽然破棺而出吕晌,到底是詐尸還是另有隱情,我是刑警寧澤临燃,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布睛驳,位于F島的核電站,受9級(jí)特大地震影響膜廊,放射性物質(zhì)發(fā)生泄漏乏沸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一爪瓜、第九天 我趴在偏房一處隱蔽的房頂上張望蹬跃。 院中可真熱鬧,春花似錦铆铆、人聲如沸蝶缀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)翁都。三九已至,卻和暖如春谅猾,著一層夾襖步出監(jiān)牢的瞬間柄慰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工税娜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坐搔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓敬矩,卻偏偏與公主長(zhǎng)得像薯蝎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谤绳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容