1. 程式人生 > >JavaScript函數的各種調用模式

JavaScript函數的各種調用模式

第一個 傳遞 小夥伴 改變 簡單的 apply() app 初始化 環境

函數是JavaScript世界裏的第一公民,換句話來說,就是我們如果可以精通JavaScript函數的使用,那麽對JavaScript的運用可以更遊刃有余了。熟悉JavaScript的人應該都知道,同樣的函數,以不同的方式調用的話,受影響最大的應該是 this 。下面我們來說說JavaScript函數的各種調用模式。

一、普通函數的調用模式

  所謂普通函數的調用模式,也是JavaScript函數的最簡單的一種調用模式,直接就是函數名後接一個 () 實現調用,看下面代碼:

function func(){
    console.log(this === window);  //true
}
func();

  上面代碼,我們用function關鍵字聲明了一個 func 函數,並且在函數體內打印 this===window,然後我們直接調用函數func,我們可以看到控制臺是直接打印出 true ,也就是說,函數的這種普通調用模式,函數體內的 this 是指向全局環境 window 的。不清楚這點的同學,可以能會遇到這樣的一個bug:

var color = ‘gg‘;
var obj = {
    color : ‘red‘,
    show : function(){
        function func1(){
            console.log(this.color);  //gg
        }
        func1();
    }
}
obj.show(); 

  我們在全局環境下聲明了一個變量 color 和一個對象 obj ,在對象 obj 裏面我們還聲明了一個 color 屬性 為 ‘red‘,一個 show 方法。而且在 show 方法裏面呢,我們還聲明了一個函數 func1 並且調用了 func1,func1 的作用是打印 this.color。最後我們運行代碼 obj.show(); 調用obj裏面的show方法。不清楚函數的普通調用模式的特點的同學可能會認為此時在控制臺答應出來的會是 ‘red‘ 。實際上此時在控制臺答應出來的應該是 gg 。因為函數 func1 的調用模式是 普通函數調用模式(即使它是在 obj 的 show 方法裏面調用的),所以此時函數體內的 this 是指向 全局環境window 的,所以就打印了全局環境下的變量 color 。

  可能有些同學會問:如果我們希望 func1 函數打印出來的是 ‘red‘ 呢,應該怎麽改?其實很簡單,因為 obj.color 才是 ‘red‘ ,所以我們只需要把 指向 obj 的 this 引入到函數 func1 裏面就行了:

var color = ‘gg‘;
var obj = {
    color : ‘red‘,
    show : function(){
        var that = this;
        function func1(){
            console.log(that.color);  //red
        }
        func1();
    }
}
obj.show();

  在上面的代碼中,因為 show 裏面的 this 指向 obj 的,所以我們在 show 裏面聲明一個變量 that = this;用來把指向 obj 的 this 引入到 func1 中,然後再把 func1 函數體內的 this.color 改為 that.color ,此時在控制臺打印出來的就是我們想要的 ‘red‘ 了。

  可能現在又有同學會問:為什麽 show 裏面的 this 是指向 obj 的呢?這就是我們要說的JavaScript函數的第二種調用模式:方法調用模式

二、方法調用模式

  方法調用模式,簡單來說就是把一個 JavaScript函數作為一個對象的方法來調用,當一個函數被保存為一個對象的屬性是,我們就把它稱為方法,例如上文的 obj 對象裏的 show ,當一個方法被調用時,函數體裏面的 this 就會綁定到這個對象,例如上文的 show 裏面的 this 。方法調用模式也很容易辨別:obj.show(),對象名 . 屬性名 () ;代碼的話可以參考上文的 obj 代碼 ,博主就不多寫了。記住:方法的調用是可以在函數體內通過 this 訪問自己所屬的那個對象的。

三、構造器調用模式

  博主認為構造器調用模式是相對於其他模式來說較為復雜點的調用模式了。通過關鍵字 new 可以把一個函數作為構造器來調用。關鍵字 new 可以改變函數的返回值:

function func2(name){
    this.name = name;
}

name;   //undefined

//普通函數調用模式
var foo = func2(‘afei‘);
foo;  //undefined
name;   //afei


//構造器調用模式
var bar = new func2(‘lizefei‘);
bar.__proto__ === func2.prototype; //true bar;
//{name:‘lizefei‘} bar.name; //‘lizefei‘

  在上示代碼中我們聲明了一個函數 func2 ,分別用兩種不同的調用模式去調用它。因為函數 func2 並沒有顯式返回值,所以作為普通函數去調用時,它什麽也沒有返回,所以 foo 的值是 undefined 。因為普通調用模式的 this 是指向 全局環境 window 的,所以 func2(‘afei‘); 後,全局環境下就多了一個 name 變量且等於 ‘afei‘。

  func2 作為構造器調用時,我們可以看到,它返回的是一個對象,因為關鍵字 new 使得函數在調用是發生了如下的特殊變化:

  1.   創建了一個新對象,而且這個新對象是鏈接到 func2 的 prototype 屬性的
  2.   把函數裏的 this 指向了這個新對象
  3.   如果沒有顯式的返回值,新對象作為構造器func2的返回值進行返回(所以bar 是 {name:‘lizefei‘})

  這樣子我們就可以看出構造器的作用:通過函數的調用來初始化新創建出來的對象。在JavaScript的面向對象編程裏面,這個可是相當重要的。

  因為在函數的聲明上,在未來作為構造器調用的函數和普通函數的聲明沒什麽區別,所以導致後來的開發者很容易因為調用模式的錯誤導致程序出問題。所以開發者們都默契地約定,用來做構造器調用的函數的函數名的第一個字符應該大寫,例如:Person,People。這樣子後來的開發者一看到函數名就知道要用構造器調用模式調用此函數了。

四、使用apply()和call()方法調用

  這種調用的模式是為了更靈活控制函數運行的上下文環境而誕生的。簡單的說就是為了靈活控制函數體內 this 的值。

  apply 和 call這兩個方法的第一個參數都是要傳遞被函數上下文的對象(簡單點說就是要綁定給函數 this 的對象)。其他參數就有所不同了:

  apply方法的第二個參數是一個數組,數組裏面的值將作為函數調用的參數;

  call方法,從第二個參數起(包括第二個參數),剩下的參數都是作為函數調用的參數;

  讓我們看看栗子:

var obj = {
    name :‘afei‘
}
function say(ag1,ag2){
    console.log(ag1+‘:‘+ag2+" "+ this.name);
}
say.apply(obj,[‘apply方法‘,‘hello‘]); //apply方法:hello afei
say.call(obj,‘call方法‘,‘hi‘); //call方法:hi afei

  正如栗子所示,我們把對象 obj 作為函數 say 的上下文來調用函數 say ,所以函數裏的 this 是指向 對象 obj 的。在apply方法裏,我們通過數組 [‘apply方法‘,‘hello‘] 給 say 方法傳遞了兩個參數(‘apply方法‘ 和 ‘hello‘),所以打印出來是: apply方法:hello afei。

  同理 call 也是一樣,而且函數傳遞的方式通過上面的代碼也一目了然我,博主就不多做解釋了。

  另外,博主還聽說apply和call這兩個方法除了傳遞參數的方式不一樣,執行的速度還是apply 比 call 要快呢。不過博主就沒有實驗過。

五、總結

  在JavaScript裏面,函數只要的調用模式就是這幾種了(在ES6裏面還有一種很奇怪很特殊的函數調用模式,叫做’標簽模板‘,在這裏博主也不多說了,有空另更),只要掌握了這幾種主要的調用模式,那麽日後再也不用擔心 this 的值變來變去了。

  上文如果有漏的、有錯誤的地方,望各位小夥伴指出,小弟虛心向學。

ps:轉載請標明出處 http://www.cnblogs.com/afeihome/

JavaScript函數的各種調用模式