8、函數(shù)——《javascript權(quán)威指南》閱讀筆記

函數(shù)

函數(shù)的一些特征

  • 函數(shù)形參類似函數(shù)內(nèi)的局部變量
  • 函數(shù)沒有return語句就返回undefined
  • 除了函數(shù)實(shí)參之外,每次調(diào)用還會(huì)有另一個(gè)值——本次調(diào)用的上下文——就是this關(guān)鍵字的值

如果函數(shù)掛載在一個(gè)對(duì)象上,作為對(duì)象的一個(gè)屬性,就稱它為對(duì)象的方法。當(dāng)通過這個(gè)對(duì)象來調(diào)用函數(shù)時(shí),該對(duì)象就是此次調(diào)用的上下文(context),也就是該函數(shù)的this的值。用于初始化一個(gè)新創(chuàng)建的對(duì)象的函數(shù)稱為構(gòu)造函數(shù)(constructor)。

在javascript里,==函數(shù)即對(duì)象==,程序可以隨意操控它們。比如,javascript可以把函數(shù)賦值給變量,或者作為參數(shù)傳遞給其他函數(shù)。因?yàn)楹瘮?shù)就是對(duì)象,所以可以給他們?cè)O(shè)置屬性,甚至調(diào)用他們的方法。

javascript的函數(shù)可以嵌套在其他函數(shù)中定義,這樣他們就可以訪問它們被定義時(shí)所處的作用域中的任何變量。這意味著javascript函數(shù)構(gòu)成了一個(gè)==閉包(closure)==,它給javascript帶來了非常強(qiáng)勁的編程能力。

展示一個(gè)小例子,閉包demo:

var Counter=(function(){  
    //賦初值
    var count=0;  
    //外部調(diào)用時(shí)形成閉包
    function f(){            
        return ++count;  
    }
    return f;
})();
Counter();//1
Counter();//2
Counter();//3

函數(shù)定義

定義函數(shù)的兩種方式:==函數(shù)聲明==和==函數(shù)表達(dá)式==,下面是一些函數(shù)使用的常用場景:

//輸出o的每個(gè)屬性的名稱和值,返回undefined
function printprops(o){
    for(var p in o)
        console.log(p + ": " + o[p] + "\n");
}

//計(jì)算兩個(gè)笛卡爾坐標(biāo)(x1,y1)和(x2,y2)之間的距離
function distance(x1,y1, x2,y2){
    var dx = x2 - x1;
    var dy = y2 - y1;
    return Math.sqrt(dx * dx + dy * dy);
}

//計(jì)算階乘的遞歸函數(shù)(調(diào)用自身的函數(shù))
//x的值是從x到x遞減的值的累乘
function factorial(x){
    if(x <= 1) return 1;
    return x * factorial(x - 1);
}

//這個(gè)函數(shù)表達(dá)式定義了一個(gè)函數(shù)用來求傳入?yún)?shù)的平方
//注意我們把它賦值給一個(gè)變量
var square = function(x){ return x * x; }

//函數(shù)表達(dá)式可以包含名稱,這在遞歸時(shí)很有用
var f = function fact(x){
    if(x <= 1) return 1;
    return x * fact(x - 1);
}

//函數(shù)表達(dá)式也可以作為參數(shù)傳給其他函數(shù)
data.sort(function(a,b){return a - b;});

//函數(shù)表達(dá)式有時(shí)定義后立即調(diào)用
var tensquared = (function(x){ return x * x; }(10));

注:

  • 前三個(gè)function是函數(shù)聲明方式,后四個(gè)是函數(shù)表達(dá)式
  • 以表達(dá)式方式定義函數(shù),函數(shù)的名稱是可選的。
  • 函數(shù)聲明語句實(shí)際上聲明了一個(gè)變量,并把一個(gè)函數(shù)對(duì)象賦值給它。定義函數(shù)表達(dá)式時(shí)并沒有聲明一個(gè)變量。
  • 函數(shù)聲明定義函數(shù)會(huì)提前到代碼頂部,就是說在函數(shù)定義之前的代碼可以調(diào)用該函數(shù)。而函數(shù)定義表達(dá)式卻沒有“提前”
  • 如果一個(gè)函數(shù)定義表達(dá)式包含名稱,函數(shù)的局部作用域?qū)?huì)包含一個(gè)綁定到函數(shù)對(duì)象的名稱。實(shí)際上,函數(shù)的名稱將成為函數(shù)內(nèi)部的一個(gè)局部變量。 (其實(shí)就是說,如果一個(gè)函數(shù)定義表達(dá)式如果包含名稱,那么函數(shù)的內(nèi)部可以通過這個(gè)名稱來訪問這個(gè)函數(shù),類似上面的階乘函數(shù))

函數(shù)命名

  • 函數(shù)名稱一般由字母、數(shù)字、美元符號(hào)和下劃線組成
  • 函數(shù)名稱通常是動(dòng)詞或動(dòng)詞為前綴的詞組。
  • 當(dāng)函數(shù)名稱包含多個(gè)單詞時(shí),一般有兩種命名方式 :liek_this(),likeThis()——camel式(小駝峰式)

在一些編程風(fēng)格中,或者編程框架里,通常==為那些經(jīng)常調(diào)用的函數(shù)指定短暫的名稱==,比如jQuery就將最常用的方法重命名為$()。

嵌套函數(shù)

p180

函數(shù)調(diào)用

構(gòu)成函數(shù)主體的javascript代碼在定義之時(shí)并不會(huì)執(zhí)行,只有在調(diào)用該函數(shù)時(shí),他們才會(huì)執(zhí)行。有4種方式來調(diào)用javascript函數(shù):

  • 作為函數(shù)(定義一個(gè)function funname(),然后執(zhí)行funname()調(diào)用該函數(shù))
  • 作為方法(對(duì)象o.funname())
  • 作為構(gòu)造函數(shù)(new funname())
  • 通過他們的call()和apply()方法簡潔調(diào)用

作為函數(shù)調(diào)用

函數(shù)如果有return語句,那么執(zhí)行到return語句返回,沒有return語句,則返回undefined。

根據(jù)ECMAScript 3和非嚴(yán)格的ECMAScript 5對(duì)函數(shù)調(diào)用規(guī)定,調(diào)用上下文(this的值)是全局對(duì)象。然而,在嚴(yán)格模式下,調(diào)用上下文則是undefined。


//'use strict'
var strict = (function(){
    return this;
}());
console.log(strict);//window
//如果取消'use strict`的注視,那么返回undefined

或者:

//'use strict'//非嚴(yán)格模式
function per(){
    var self = this;//Window(非嚴(yán)格模式)
}
per('');
//去掉第一行注視(嚴(yán)格模式下),pre里面的self變量等于undefined了。

其實(shí)很好理解,因?yàn)檎{(diào)用(上面提到的作為函數(shù)調(diào)用)pre函數(shù)的是window對(duì)象,所以this是window(嚴(yán)格模式等于undefined是規(guī)定的,死記就好)。

其實(shí)這些是有章可循的,即看這個(gè)函數(shù)被當(dāng)作什么來調(diào)用,從而可以確定它(函數(shù)內(nèi)部的this對(duì)象)的所有者,this就指代這個(gè)所有者。函數(shù)里面的this對(duì)象指代規(guī)則如下:

  • 作為函數(shù)調(diào)用,(嚴(yán)格模式下this是undefined,非嚴(yán)格模式下是window)
  • 作為方法調(diào)用,this是調(diào)用當(dāng)前方法的對(duì)象
  • 作為構(gòu)造函數(shù)調(diào)用,this是當(dāng)前new出來的對(duì)象
  • 通過函數(shù)的call()或apply()調(diào)用,this就是傳遞進(jìn)去的對(duì)象(這兩個(gè)方法主要用來改變函數(shù)內(nèi)部的thi對(duì)象)

以函數(shù)形式調(diào)用的函數(shù)通常不使用this關(guān)鍵字(有時(shí)候也會(huì)這么做,當(dāng)你需要寫一個(gè)js的基類,然后很多子類都會(huì)繼承該類,并可以在子類里面覆蓋基類時(shí),通過這種方法可以減少很多重復(fù)性的代碼)。不過,“this”可以用來判斷當(dāng)前是否是嚴(yán)格模式。

//定義并調(diào)用一個(gè)函數(shù)來確定當(dāng)前腳本運(yùn)行時(shí)是否為嚴(yán)格模式
var strict = (function(){
    return !this;
}());

注:函數(shù)調(diào)用這里的函數(shù)指不依賴或不掛載在其他對(duì)象下面的方法,就是一個(gè)簡簡單單的 function定義的表達(dá)式。

作為方法調(diào)用

一個(gè)方法無非是個(gè)保存在一個(gè)對(duì)象的屬性里面的javascript函數(shù)。如果有一個(gè)函數(shù)f和一個(gè)對(duì)象o,則可以用下面的代碼給o定義一個(gè)名為m()的方法。

o.m = f;

給對(duì)象o定義了方法m(),調(diào)用它時(shí)就像這樣:

o.m();

上面代碼意味著該函數(shù)被當(dāng)作一個(gè)方法,而不是作為一個(gè)普通函數(shù)來調(diào)用。

==對(duì)方法調(diào)用的參數(shù)和返回值的處理,和上面所描述的普通函數(shù)調(diào)用完全一致。但是,方法調(diào)用和函數(shù)調(diào)用有一個(gè)重要區(qū)別==,即:==調(diào)用上下文==。屬性訪問表達(dá)式由兩部分組成:一個(gè)對(duì)象(本例中的o)和屬性名稱(m)。在像這樣的方法調(diào)用表達(dá)式里,對(duì)象o成為調(diào)用上下文,函數(shù)體可以使用關(guān)鍵字this引用該對(duì)象。下面是一個(gè)具體的例子:

var calculator = { //對(duì)象直接量
    operand1: 1,
    operand2: 1,
    add: function(){
        //注意this關(guān)鍵字的用法,this指代當(dāng)前對(duì)象
        this.result = this.operand1 + this.operand2;
    }
};
calculator.add();   //這個(gè)方法調(diào)用計(jì)算1+1的結(jié)果
calculator.result;  //返回2

方法鏈:當(dāng)方法的返回值是一個(gè)對(duì)象,這個(gè)對(duì)象還可以再調(diào)用它的方法。這種方法調(diào)用序列中(通常稱為“鏈”或者“級(jí)聯(lián)”)每次的調(diào)用結(jié)果都是另外一個(gè)表達(dá)式的組成部分。比如,基于jQuery庫,我們常常會(huì)這樣寫代碼:

//找到所有的header,取得它們id的映射,轉(zhuǎn)換為數(shù)組并對(duì)它們進(jìn)行排序
$(":header").map(function(){return this.id}).get().sort();

==當(dāng)方法并不需要返回值時(shí),最好直接返回this==。如果在設(shè)計(jì)API就可以進(jìn)行“鏈?zhǔn)秸{(diào)用”風(fēng)格編程。在這種編程風(fēng)格中,只要指定一次要調(diào)用的對(duì)象即可,余下的方法都可以基于此進(jìn)行調(diào)用:

shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw();

自己寫的一個(gè)demo:

function shape(){
    var self = this;
    self.x = 0;
    self.y = 0;
    self.size = 0;
    self.setX = function(_x){
        self.x = _x;
        return self;
    };
    self.setY = function(_y){
        self.y = _y;
        return self;
    };
    self.setSize = function(_size){
        self.size = _size;
        return self;
    };
    self.getX = function(){return self.x; };
    self.getY = function(){return self.y;};
    self.getSize = function(){return self.size;};
}

var s1 = new shape();
s1.setX(1).setY(2).setSize(3);//鏈?zhǔn)綄懛?var x = s1.getX();//1
var y = s1.getY();//2
var size = s1.getSize();//3

需要注意的是,this是一個(gè)關(guān)鍵字,不是變量,也不是屬性名。javascript的語法不允許給this賦值。

和變量不同,關(guān)鍵字this沒有作用域的限制,嵌套的函數(shù)不會(huì)從調(diào)用它的函數(shù)中繼承this。如果嵌套函數(shù)作為方法調(diào)用,其this的值指向調(diào)用它的對(duì)象。如果嵌套函數(shù)作為函數(shù)調(diào)用,其this值不是全局對(duì)象(非嚴(yán)格模式下)就是undefined(嚴(yán)格模式下)。很多人誤以為調(diào)用嵌套函數(shù)時(shí),this會(huì)指向調(diào)用外層函數(shù)的上下文。如果你想訪問這個(gè)外部函數(shù)的this值,需要將this的值保存在一個(gè)變量里,這個(gè)變量和內(nèi)部函數(shù)都在同一個(gè)作用域內(nèi)。通常使用變量self來保存this,比如(理解這段話,最好先看明白如下例子):

var o = {                           //對(duì)象o
    m: function(){                  //對(duì)象中的方法m()
        var self = this;            //將this的值保存至一個(gè)變量中
        console.log(this === o);    //輸出true,this就是這個(gè)對(duì)象o
        f();                        //調(diào)用輔助函數(shù)f()——將f作為函數(shù)調(diào)用
        
        function f(){               //定義一個(gè)嵌套函數(shù)
            console.log(this === o);//false:this的值是全局對(duì)象或undefined
            console.log(self === o);//true:self值外部函數(shù)的this值
        }
    }
};
o.m(); //調(diào)用對(duì)象o的方法m()

結(jié)論:

  • 嵌套函數(shù)中,外部函數(shù)的this和內(nèi)部(嵌套)函數(shù)內(nèi)部的this是兩個(gè)不同的對(duì)象,并且都通過this引用該對(duì)象,如果想在嵌套函數(shù)內(nèi)部調(diào)用外部函數(shù)的this對(duì)象,必須將外部函數(shù)保存到一個(gè)變量——不然內(nèi)部函數(shù)this關(guān)鍵字只哪個(gè)呢,記住,他們都通過this關(guān)鍵字調(diào)用的。
  • (內(nèi)部)函數(shù)里面的this對(duì)象指代什么,依賴上面提到的(函數(shù)被當(dāng)作什么來調(diào)用)。實(shí)際上可能應(yīng)該是所有函數(shù)里面的this對(duì)象指代什么都需要看當(dāng)前函數(shù)被當(dāng)作什么來調(diào)用。

作為構(gòu)造函數(shù)調(diào)用

==構(gòu)造函數(shù)調(diào)用和普通的函數(shù)調(diào)用以及方法調(diào)用在實(shí)參處理、調(diào)用上下文和返回值方面都有不同。==

如果構(gòu)造函數(shù)沒有形參,那么小括號(hào)是可以省略的,比如,下面這兩行代碼就是等價(jià)的。

var o = new Object();
var o = new Object;

==構(gòu)造函數(shù)調(diào)用創(chuàng)建一個(gè)新的空對(duì)象,這個(gè)對(duì)象繼承自構(gòu)造函數(shù)的prototype屬性。構(gòu)造函數(shù)試圖初始化這個(gè)新創(chuàng)建的對(duì)象,并將這個(gè)對(duì)象用做其調(diào)用上下文,因此構(gòu)造函數(shù)可以使用this關(guān)鍵字來引用新創(chuàng)建的對(duì)象。== 注意,盡管構(gòu)造函數(shù)看起來像一個(gè)方法調(diào)用,它依然會(huì)使用這個(gè)新對(duì)象作為調(diào)用上下文。也就是說,在表達(dá)式new o.m()中,調(diào)用上下文并不是o。

輔助解讀:

上面提到“這個(gè)對(duì)象繼承自”,繼承自哪里?可以通過對(duì)象.__proto__得到,如下:

function shape(){}
var s1 = new shape();
console.log(s1.__proto__ === shape.prototype);//true
console.log(s1 instanceof shape);//true

間接調(diào)用

javascript中的函數(shù)也是對(duì)象,和其他javascript對(duì)象沒什么兩樣,函數(shù)對(duì)象也可以包含方法。其中的兩個(gè)方法call()和apply()可以用來間接地調(diào)用函數(shù)。兩個(gè)方法都允許顯示指定調(diào)用所需的this值,也就是說,任何函數(shù)可以作為任何對(duì)象的方法來調(diào)用,哪怕這個(gè)函數(shù)不是那個(gè)對(duì)象的方法。兩個(gè)方法都可以指定調(diào)用的實(shí)參。call()方法使用它自由的實(shí)參列表作為函數(shù)的實(shí)參。apply()方法則要求以數(shù)組的形式傳入?yún)?shù)。

函數(shù)的實(shí)參和形參

javascript中的函數(shù)定義并未指定函數(shù)形參的類型,函數(shù)調(diào)用也未對(duì)傳入的實(shí)參做任何類型檢查。實(shí)際上,javascript函數(shù)調(diào)用甚至不檢查傳入形參的個(gè)數(shù)。下面幾節(jié)將討論當(dāng)調(diào)用函數(shù)時(shí)的形參和實(shí)參個(gè)數(shù)不匹配時(shí)出現(xiàn)的狀況,同樣說明了如何顯示測試函數(shù)實(shí)參的類型,以避免非法的實(shí)參傳入函數(shù)。

可選形參

==當(dāng)調(diào)用函數(shù)的時(shí)候傳入的實(shí)參比函數(shù)聲明時(shí)指定的形參個(gè)數(shù)要少,剩下的形參都將設(shè)置為undefined值==。因此在調(diào)用函數(shù)時(shí)形參是否可選以及是否可以省略應(yīng)該保持較好的適用性。==為了做到這一點(diǎn),應(yīng)該給省略的參數(shù)賦一個(gè)合理的默認(rèn)值==,看下面的例子:

//將對(duì)象o中可枚舉的屬性名追加至數(shù)組a中,并返回這個(gè)數(shù)組a
//如果省略a,則創(chuàng)建一個(gè)新數(shù)組并返回這個(gè)新數(shù)組
function getPropertyNames(o,/* optional */ a){
    if(a === undefined) a = []; //如果為定義,則使用新數(shù)組
    // a = a || [];             //同上面的if語句

    for(var property in o)
        a.push(property);
    return a;
}

//這個(gè)函數(shù)調(diào)用可以傳入1個(gè)或2個(gè)實(shí)參
var a = getPropertyNames(o) //將o的屬性存儲(chǔ)到一個(gè)新數(shù)組中
getPropertyNames(p,a);      //將p的屬性追加至數(shù)組a中

需要注意的是,當(dāng)用這種可選實(shí)參來實(shí)現(xiàn)函數(shù)時(shí),需要將可選實(shí)參放在實(shí)參列表的最后。因?yàn)閖s沒法省略第一個(gè)實(shí)參并傳入第二實(shí)參的,它必須將undefined作為第一個(gè)實(shí)參顯示傳入。同樣注意在函數(shù)定義中使用注釋/*optional*/來強(qiáng)調(diào)形參是可選的。

可變長的實(shí)參列表:實(shí)參對(duì)象

當(dāng)調(diào)用函數(shù)的時(shí)候傳入的實(shí)參個(gè)數(shù)超過函數(shù)定義時(shí)的形參個(gè)數(shù)時(shí)。沒有辦法直接獲得未命名值的引用。參數(shù)對(duì)象解決了這個(gè)問題。在函書體內(nèi),標(biāo)識(shí)符arguments是指向?qū)崊?duì)象的引用,實(shí)參對(duì)象是一個(gè)類數(shù)組對(duì)象,這樣可以通過數(shù)字下標(biāo)就能訪問傳入函數(shù)的實(shí)參值,而不用非要通過名字來得到實(shí)參。

實(shí)參對(duì)象在很多地方都非常有用,下面的例子展示了使用它來驗(yàn)證實(shí)參的個(gè)數(shù),從而調(diào)用正確的邏輯,因?yàn)閖avascript本身不會(huì)這么做:

function f(x, y, z){
    //首先,驗(yàn)證傳入實(shí)參的個(gè)數(shù)是否正確
    if(arguments.length != 3){
        throw new Error("函數(shù)f使用" + arguments.length + "個(gè)參數(shù),但它期望有3個(gè)參數(shù)");
    }
    //再執(zhí)行函數(shù)的其他邏輯……
}

需要注意的是,通常不必像這樣檢查實(shí)參個(gè)數(shù)。大多數(shù)情況下javascript的默認(rèn)行為是可以滿足需求的:==省略的實(shí)參都將是undefined,多出的參數(shù)會(huì)自動(dòng)省略==。

實(shí)參對(duì)象有一個(gè)重要的用處,就是讓函數(shù)可以操作任何數(shù)量的實(shí)參。下面的函數(shù)就可以接收任意數(shù)量的實(shí)參,并返回傳入實(shí)參的最大值(內(nèi)置函數(shù)Max.max()的功能與之類似):

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;
}
var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6);

注:arguments==并不是真正的數(shù)組,它是一個(gè)實(shí)參對(duì)象。每個(gè)實(shí)參對(duì)象都包含以數(shù)字為索引的一組元素以及l(fā)ength屬性,但它畢竟不是真正的數(shù)組??梢赃@樣理解,它是一個(gè)對(duì)象,只是碰巧具有以數(shù)字為索引的屬性。==

輔助解讀(證明arguments對(duì)象并不是數(shù)組):

function fun(){
    //[object Array]
    console.log(Object.prototype.toString.apply([]));
    //[object Arguments]
    console.log(Object.prototype.toString.apply(arguments));
}
fun();

只是一個(gè)key類似索引的對(duì)象:


aaa.png

緊要了。為了實(shí)現(xiàn)這種風(fēng)格的方法調(diào)用,定義函數(shù)的時(shí)候,傳入的實(shí)參都寫入一個(gè)單獨(dú)的對(duì)象之中,在調(diào)用的時(shí)候傳入一個(gè)對(duì)象,對(duì)象中的key/value對(duì)是真正需要的實(shí)參數(shù)據(jù)。下面就展示了這種風(fēng)格:

//將原始數(shù)組的length元素復(fù)制至目標(biāo)數(shù)組
//開始復(fù)制原始數(shù)組的from_start元素
//并且將其復(fù)制至目標(biāo)數(shù)組的to_start中
//要記住實(shí)參的順序并不容易
function arraycopy(/* array */ from, /* index */ from_start,
                    /* array */ to, /* index */ to_start,
                    /* integer */ length){
    //邏輯代碼
}

//這個(gè)版本的實(shí)現(xiàn)效率稍微有點(diǎn)底,但你不必再去記住實(shí)參的順序
//并且from_start和to_start都默認(rèn)為0
function easycopy(args){
    arraycopy(args.from,
              args.from_start || 0,//注意這里設(shè)置了默認(rèn)值 
              argt.to,
              args.to_start || 0, args.length );
}

//來看如何調(diào)用easycopy()
var a = [1, 2, 3, 4], b = [];
easycopy({from:a, to: b, length:4});//省略了兩個(gè)參數(shù)

實(shí)參類型

因?yàn)閖avascript沒有嚴(yán)格的參數(shù)類型限制,==所以我們應(yīng)當(dāng)添加一些類似的實(shí)參類型檢查邏輯,因?yàn)閷幵赋绦蛟趥魅敕欠ㄖ禃r(shí)報(bào)錯(cuò),也不愿非法值導(dǎo)致程序在執(zhí)行時(shí)報(bào)錯(cuò),相比而言,邏輯執(zhí)行時(shí)的報(bào)錯(cuò)不甚清晰且更難處理。==

//返回?cái)?shù)組(或類數(shù)組對(duì)象)a的元素的累加和
//數(shù)組a中必須為數(shù)字、null和undefined的元素都將忽略
function sum(a){
    if(isArrayLike(a)){
        var total = 0;
        for(var i = 0; i < a.length; i++){//遍歷所有元素
            var element = a[i];
            if(element == null) continue;//跳過null和undefined
            if(isFinite(element)) total += element;
            else throw new Error("sum(): 數(shù)組a中的元素必須是一個(gè)有效數(shù)字 ");
        }
        return total;
    }else{
        throw new Error("參數(shù)a必須是數(shù)組對(duì)象");
    }
}

作為值的函數(shù)

==函數(shù)不僅是一種語法。也是值,也就是說,可以將函數(shù)賦值給變量,存儲(chǔ)在對(duì)象的屬性或數(shù)組的元素中,作為參數(shù)傳入另外一個(gè)函數(shù)等。==

為了便于理解javascript中的函數(shù)時(shí)如何用做數(shù)據(jù)的以及javascript語法,來看一下這樣一個(gè)函數(shù)定義:

function square(x){ return x * x; }

這個(gè)定義創(chuàng)建一個(gè)新的函數(shù)對(duì)象,并將其賦值給變量square。==函數(shù)的名字實(shí)際上是看不見的,它(square)僅僅是變量的名字,這個(gè)變量指代函數(shù)對(duì)象。== 函數(shù)還可以賦值給其他的變量,并且仍可以正常工作:

var s = square; //現(xiàn)在s和square指代同一個(gè)函數(shù)
square(4);      //16
s(4);           //16

自定義函數(shù)屬性

javascript中的函數(shù)并不是原始值,而是一種特殊的對(duì)象,也就是說,函數(shù)可以擁有屬性。當(dāng)函數(shù)需要一個(gè)“靜態(tài)”變量來在調(diào)用時(shí)保持某個(gè)值不變,最方便的方式就是給函數(shù)定義屬性,而不是定義全局變量,顯然定義全局變量會(huì)讓命名空間變得更加雜亂無章。比如,假設(shè)你想寫一個(gè)返回一個(gè)唯一整數(shù)的函數(shù),不管在哪里調(diào)用函數(shù)都會(huì)返回這個(gè)整數(shù)。而函數(shù)不能兩次返回同一個(gè)值,為了做到這一點(diǎn),函數(shù)必須能夠跟蹤它每次返回的值,而且這些值的信息需要在不同的函數(shù)調(diào)用過程中持久化。可以將這些信息存放到全局變量中,但這并不是必需的,因?yàn)檫@個(gè)信息僅僅是函數(shù)本身用到的。最好將這個(gè)信息保存到函數(shù)對(duì)象的一個(gè)屬性中,下面這個(gè)例子就實(shí)現(xiàn)了這樣的函數(shù),每次調(diào)用函數(shù)都返回一個(gè)唯一的整數(shù):

//初始化函數(shù)對(duì)象的計(jì)數(shù)器屬性
//由于函數(shù)聲明被提前了,因此這里是可以在函數(shù)聲明
//之前給它的成員復(fù)制的
uniqueInteger.counter = 0;

//每次調(diào)用這個(gè)函數(shù)都回返回一個(gè)不同的整數(shù)
//它使用一個(gè)屬性來記住下一次將要返回的值
function uniqueInteger(){
    console.log(uniqueInteger.counter);
    return uniqueInteger.counter++;
}

new uniqueInteger();//output 0
new uniqueInteger();//output 1
new uniqueInteger();//output 2

作為命名空間的函數(shù)

前面已經(jīng)說到過函數(shù)作用域的概念:在函數(shù)中聲明的變量在整個(gè)函數(shù)體內(nèi)都是可見的(包括在嵌套的函數(shù)中),在函數(shù)的外部是不可見的。不在任何函數(shù)內(nèi)聲明的變量是全局變量,在整個(gè)javascript程序中都是可見的。在javascript中是無法聲明只在一個(gè)代碼塊內(nèi)可見的變量的(只有函數(shù)作用域,沒有塊級(jí)作用域)?;谶@個(gè)原因,我們常常簡單地定義一個(gè)函數(shù)用作臨時(shí)的命名空間,==在這個(gè)命名空間內(nèi)定義的變量都不會(huì)污染到全局命名空間==。

比如,假設(shè)你寫了一段javascript模塊代碼,這段代碼將要用在不同的javascript程序中。和大多數(shù)代碼一樣,假定這段代碼定義了一個(gè)用以存儲(chǔ)中間計(jì)算結(jié)果的變量。這樣問題就來了,當(dāng)模塊代碼放到不同的程序中運(yùn)行時(shí),你無法得知這個(gè)變量是否已經(jīng)創(chuàng)建了,如果已經(jīng)存在這個(gè)變量,那么將會(huì)和代碼發(fā)生沖突。解決辦法當(dāng)然是將代碼放入一個(gè)函數(shù)內(nèi),然后調(diào)用這個(gè)函數(shù)。這樣全局變量就變成了函數(shù)內(nèi)的局部變量:

function mymodule(){
    //模塊代碼
    //這個(gè)模塊所用的所有變量都是局部變量
    //而不會(huì)污染全局命名空間
}
mymodule();//不要忘了還要調(diào)用這個(gè)函數(shù)

這段代碼僅僅定義了一個(gè)單獨(dú)的全局變量:名叫“mymodule”的函數(shù)。這樣還是太麻煩,可以直接定義一個(gè)匿名函數(shù),并在單個(gè)表達(dá)式中調(diào)用它:

(function(){//mymodule()函數(shù)重寫為匿名的函數(shù)表達(dá)式
    //模塊代碼
}());       //結(jié)束函數(shù)定義并立即調(diào)用它

這種定義匿名函數(shù)并立即在單個(gè)表達(dá)式中調(diào)用它的寫法非常常見,已經(jīng)成為一種慣用法了。注意上面代碼的圓括號(hào)的用法,function之前的做圓括號(hào)是必須的,因?yàn)槿绻粚?,javascript解釋器會(huì)試圖將關(guān)鍵字function解析為函數(shù)聲明語句。使用圓括號(hào)javascript解析器才會(huì)解析為函數(shù)定義表達(dá)式。

輔助解讀:

實(shí)際上,如果沒有左圓括號(hào)是會(huì)報(bào)錯(cuò)的

function(){}() //SyntaxError
(function(){}()) //下面這兩種都可以
(function(){})()

p193 函數(shù)自定義屬性這段不太了解

閉包

p194

閱讀這一小節(jié)之前建議先復(fù)習(xí)《javascript權(quán)威指南》第 3.10(變量作用域)、3.10.3(作用域鏈)。

并參考:JavaScript 開發(fā)進(jìn)階:理解 JavaScript 作用域和作用域鏈

閉包的最常見形式:當(dāng)一個(gè)函數(shù)嵌套了另外一個(gè)函數(shù),外部函數(shù)將嵌套的函數(shù)對(duì)象作為返回值返回的時(shí)候就是一個(gè)閉包的形式。

文章開頭給了一個(gè)閉包的小demo,這里我復(fù)制以下:

var Counter=(function(){  
    //賦初值
    var count=0;  
    //外部調(diào)用時(shí)形成閉包
    function f(){            
        return ++count;  
    }
    return f;
})();
Counter();//1
Counter();//2
Counter();//3

這段代碼剛好就是上面對(duì)閉包描述的代碼表示形式了。

總結(jié),閉包的特點(diǎn):

  • 兩個(gè)函數(shù)嵌套;
  • 調(diào)用外部函數(shù)返回內(nèi)部函數(shù)(內(nèi)部函數(shù)引用,不是執(zhí)行結(jié)果);
  • 內(nèi)部函數(shù)引用外部函數(shù)變量(這是閉包的用處之一——這樣做的目的是:雖然外部函數(shù)執(zhí)行結(jié)束了,但是函數(shù)的變量并沒有被回收)

注意:上面代碼并不能簡寫成如下方式:

var Counter=(function(){  
    //賦初值
    var count=0;  
    //外部調(diào)用時(shí)形成閉包
    function f(){            
        return ++count;  
    }
    return f;
});
//看上去和上面調(diào)用的方式差不多,但其實(shí)不然:
//上面的Counter對(duì)象保存的是一個(gè)執(zhí)行后的結(jié)果(對(duì)函數(shù)f的引用),然后多次Counter()都是調(diào)用f()函數(shù);
//而這種方式雖然也是多用調(diào)用f(),但是也多次重新調(diào)用Counter去獲取對(duì)f的引用了。
Counter()();//1
Counter()();//1
Counter()();//1

可以改成如下方式:

var Counter=(function(){  
    //賦初值
    var count=0;  
    //外部調(diào)用時(shí)形成閉包
    function f(){            
        return ++count;  
    }
    return f;
});
var temp = Counter();//將對(duì)f的引用保留起來
//然后執(zhí)行該引用(方法)
temp();//1
temp();//2
temp();//3

函數(shù)屬性、方法和構(gòu)造函數(shù)

p 200

length屬性

函數(shù)的length屬性代表函數(shù)形參個(gè)數(shù)。

prototype屬性

call()方法和apply()方法

我們可以將call()和apply()看作是某個(gè)對(duì)象的方法,通過調(diào)用方法的形式來間接調(diào)用函數(shù)。call()和apply()的第一個(gè)實(shí)參是要調(diào)用函數(shù)的母對(duì)象,它是調(diào)用上下文,在函數(shù)體內(nèi)通過this來獲取對(duì)它的引用。要想以對(duì)象o的方法來調(diào)用函數(shù)f()(即:o.f
()的方式調(diào)用f()函數(shù)),可以這樣使用call()和apply():

function f(){
    var self = this;
    self.name = "aaaa";
}
var o = {name:'alleanxu'};
f.call(o);
f.apply(o);

注:這么調(diào)用其實(shí)就是為了改變函數(shù)f()中this的值,call()、apply()第一個(gè)參數(shù)即為這個(gè)this。

在ECMAScript 5的嚴(yán)格模式中,call()和apply()的第一個(gè)實(shí)參都會(huì)變?yōu)閠his的值,哪怕傳入的實(shí)參是原始值甚至是null或undefined。在ECMAScript 3和非嚴(yán)格模式中,傳入的null和undefined都會(huì)被全局對(duì)象代替,而其他原始值則會(huì)被相應(yīng)的包裝對(duì)象(wrapper objcet)所替代。

apply() 和 call() 方法的作用是類似的,只是參數(shù)不一樣:

如下:

function f(num1, num2){
    var self = this;
    self.name = "aaaa";
}
var o = {name:'alleanxu'};
f.call(o,1,2);//
f.apply(o,[1,2]);//

第一個(gè)參數(shù)是o,想要以該對(duì)象調(diào)用函數(shù)f();第二個(gè)參數(shù),call()是一個(gè)任意長度的參數(shù),而apply()是一個(gè)任意長度的數(shù)組(類數(shù)組也行,例如函數(shù)的 arguments 對(duì)象可以當(dāng)作apply()的第二個(gè)參數(shù))

//將對(duì)象o中名為m()的方法替換為另一個(gè)方法
//可以在調(diào)用原始的方法之前和之后記錄日志信息
function trace(o, m){
    var original = o[m];    //在閉包中保存原始方法
    o[m] = function(){      //定義新的方法
        console.log(new Date(), "Entering:", m);        //輸出日志消息
        var result = original.apply(this, arguments);   //調(diào)用原始函數(shù)
        console.log(new Date(), "Exiting:", m);         //輸出日志消息
        return result;                                  //返回結(jié)果
    }
}


var o = {
    name: "alleanxu",
    sayHello: function(msg){
        console.log(msg + "," + this.name);
    }
};

var temp = trace(o, "sayHello");
var res = o.sayHello("hello");

trace()函數(shù)接受兩個(gè)參數(shù),一個(gè)對(duì)象和一個(gè)方法名,它將指定的方法替換為一個(gè)新方法,這個(gè)新方法是“包裹”原始方法的另一個(gè)“泛函數(shù)”。這種動(dòng)態(tài)修改已有方法的做法有時(shí)稱做“monkey-patching”。

參考:monkey-patching(猴子補(bǔ)?。?/a>

bind()方法

bind()是在ECMAScript 5中新增的方法,但在ECMAScript 3中可以輕易模擬bind()。從名字就可以看出,這個(gè)方法的主要作用就是將函數(shù)綁定至某個(gè)對(duì)象。當(dāng)在函數(shù)f()上調(diào)用bind()方法并傳入一個(gè)對(duì)象o作為參數(shù),這個(gè)方法將返回一個(gè)新的函數(shù)。(以函數(shù)調(diào)用的方式)調(diào)用新的函數(shù)將會(huì)把原始的函數(shù)f()當(dāng)作o的方法來調(diào)用。傳入新函數(shù)的任何實(shí)參都將傳入原始函數(shù),比如:

function f(msg){
    console.log(msg);
}
var o = {};
//將函數(shù)f()綁定至對(duì)象o
//擋在函數(shù)f()上調(diào)用bind()方法并傳入一個(gè)對(duì)象o作為參數(shù),這個(gè)方法將返回一個(gè)新的函數(shù)。
var result = f.bind(o);
//以函數(shù)調(diào)用的方式調(diào)用新的函數(shù)
//將會(huì)把原始函數(shù)f()當(dāng)作o的方法來調(diào)用
//傳入新函數(shù)的任何實(shí)參都將傳入原始函數(shù)
result("hello");

或者如下代碼(也演示了bind的使用) :

function f(y){ return this.x + y ;} //這個(gè)是待綁定的函數(shù)
var o = {x: 1};                     //將要綁定的對(duì)象
var g = f.bind(o);                  //通過調(diào)用g(x)來調(diào)用o.f(x)
g(2);                               //3

可以通過如下代碼輕易地實(shí)現(xiàn)這種綁定:

//返回一個(gè)函數(shù),通過調(diào)用它來調(diào)用o中的方法f(),傳遞它所有的實(shí)參
function bind(f, o){
    if(f.bind) return f.bind(o);    //如果bind()方法存在的話,使用bind()方法
    else return function(){         //否則,這樣綁定
        return f.apply(o, arguments);
    };
}

toString()方法

p204

Function()構(gòu)造函數(shù)

......

函數(shù)式編程

p206

......

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容