js中bind、call、apply函數的用法

最近一直在用 js 寫遊戲服務器,我也接觸 js 時間不長,大學的時候用 js 作過一個 H3C 的 web 的項目,而後在騰訊實習的時候用 js 寫過一些奇怪的程序,本身也用 js 寫過幾個的網站。但真正大規模的使用 js 這仍是第一次。我也是初生牛犢不怕虎,此次服務器竟然拋棄 C++lua 的正統搭配,而嘗試用 nodejs 來寫遊戲服務器,折騰的本身要死要活的我也是醉了。
javascript

在給咱們項目組的其餘程序介紹 js 的時候,我準備了不少的內容,但看起來效果不大,果真光講仍是不行的,必須動手。前幾天有人問我關於代碼裏 call() 函數的用法,我讓他去看書,這裏推薦用 js 寫服務器的程序猿看 《javascript編程精粹》 這本書,crockford大神果真不是蓋的。以後我在 segmentfault 上又看到了相似的問題,那邊解答以後乾脆這裏記一筆。java


首先,關於 js 定義類或對象的方法,請參看 w3school 的這裏,寫的很是詳細和清晰,我再也不贅言了。node


爲了介紹 bindcallapply 這三個函數的用法,不得不介紹 js 裏函數的一些設定。關於這部分推薦通讀 《javascript編程精粹》 的第四章,這裏我所說的在書裏都能找到。web

關於這三個函數的詳細介紹,能夠參看 MDN 的文檔:bindcallapply編程


下面開始搬磚,修改自我以前在 segmentfault 上的答案:segmentfault

js 裏函數調用有 4 種模式:方法調用正常函數調用構造器函數調用apply/call 調用
同時,不管哪一種函數調用除了你聲明時定義的形參外,還會自動添加 2 個形參,分別是 thisarguments
arguments 不涉及到上述 3 個函數,因此這裏只談 thisthis 的值,在上面 4 中調用模式下,分別會綁定不一樣的值。分別來講一說:
方法調用
這個很好理解,函數是一個對象的屬性,好比
數組

var a = {    
    v : 0,    
    f : function(xx) {                
        this.v = xx;    
    }
}
a.f(5);

這個時候,上面函數裏的 this 就綁定的是這個對象 a。因此 this.v 能夠取到對象 a 的屬性 v瀏覽器

正常函數調用:
依然看代碼
服務器

function f(xx) {        
    this.x = xx;
}
f(5);

這個時候,函數 f 裏的 this 綁定的是全局對象,若是是在瀏覽器運行的解釋器中,通常來講是 window 對象。因此這裏 this.x 訪問的實際上是 window.x ,固然,若是 window 沒有 x 屬性,那麼你這麼一寫,按照 js 的坑爹語法,就是給 window 對象添加了一個 x 屬性,同時賦值。app

構造器函數調用
構造函數一直是我認爲是 js 裏最坑爹的部分,由於它和 js 最初設計的基於原型的面向對象實現方式格格不入,就好像是特地爲了迎合你們已經被其餘基於類的面相對象實現給慣壞了的習慣。
若是你在一個函數前面帶上 new 關鍵字來調用,那麼 js 會建立一個 prototype 屬性是此函數的一個新對象,同時在調用這個函數的時候,把 this 綁定到這個新對象上。固然 new 關鍵字也會改變 return 語句的行爲,不過這裏就不談了。看代碼

function a(xx) {        
    this.m = xx;
}
var b = new a(5);

上面這個函數和正常調用的函數寫法上沒什麼區別,只不過在調用的時候函數名前面加了關鍵字 new 罷了,這麼一來,this 綁定的就再也不是前面講到的全局對象了,而是這裏說的建立的新對象,因此說這種方式其實很危險,由於光看函數,你不會知道這個函數究竟是準備拿來當構造函數用的,仍是通常函數用的。因此咱們能夠看到,在 jslint 裏,它會要求你寫的全部構造函數,也就是一旦它發現你用了 new 關鍵字,那麼後面那個函數的首字母必須大寫,這樣經過函數首字母大寫的方式來區分,我我的只有一個見解:坑爹:)

apply/call 調用:
咱們知道,在 js 裏,函數其實也是一個對象,那麼函數天然也能夠擁有它本身的方法,有點繞,在 js 裏,每一個函數都有一個公共的 prototype —— Function,而這個原型自帶有好幾個屬性和方法,其中就有這裏困惑的 bindcallapply 方法。先說 apply 方法,它讓咱們構造一個參數數組傳遞給函數,同時能夠本身來設置 this 的值,這就是它最強大的地方,上面的 3 種函數調用方式,你能夠看到,this 都是自動綁定的,沒辦法由你來設,當你想設的時候,就能夠用 apply() 了。apply 函數接收 2 個參數,第一個是傳遞給這個函數用來綁定 this 的值,第二個是一個參數數組。看代碼

function a(xx) {        
    this.b = xx;
}
var o = {};
a.apply(o, [5]);
alert(a.b);    // undefined
alert(o.b);    // 5

是否是很神奇,函數 a 竟然能夠給 o 加屬性值。固然,若是你 apply 的第一個參數傳遞 null,那麼在函數 a 裏面 this 指針依然會綁定全局對象。

call() 方法和 apply() 方法很相似,它們的存在都是爲了改變 this 的綁定,那 call()apply() 有什麼區別呢?就我我的看來,沒啥鳥區別。。。開玩笑!剛剛說了,上面 apply() 接收兩個參數,第一個是綁定 this 的值,第二個是一個參數數組,注意它是一個數組,你想傳遞給這個函數的全部參數都放在數組裏,而後 apply() 函數會在調用函數時自動幫你把數組展開。而 call() 呢,它的第一個參數也是綁定給 this 的值,可是後面接受的是不定參數,而再也不是一個數組,也就是說你能夠像平時給函數傳參那樣把這些參數一個一個傳遞。因此若是必定要說有什麼區別的話,看起來是這樣的

function a(xx, yy) {    
    alert(xx, yy);    
    alert(this);    
    alert(arguments);
}
a.apply(null, [5, 55]);
a.call(null, 5, 55);

僅此而已。

最後再來講 bind() 函數,上面講的不管是 call() 也好, apply() 也好,都是立馬就調用了對應的函數,而 bind() 不會, bind() 會生成一個新的函數,bind() 函數的參數跟 call() 一致,第一個參數也是綁定 this 的值,後面接受傳遞給函數的不定參數。 bind() 生成的新函數返回後,你想何時調就何時調,看下代碼就明白了

var m = {	
    "x" : 1
};
function foo(y) {	
    alert(this.x + y);
}
foo.apply(m, [5]);
foo.call(m, 5);
var foo1 = foo.bind(m, 5);
foo1();

末了來個吐槽,你在 js 裏想定義一個函數,因而你會這麼寫:

function jam() {};

其實這是 js 裏的一種語法糖,它等價於:

var jam = function() {};

而後你想執行這個函數,腦洞大開的你會這麼寫:

function jam() {}();

可是這麼寫就報錯了,其實這種寫法也不算錯,由於它確實是 js 支持的函數表達式,可是同時 js 又規定以 function 開頭的語句被認爲是函數語句,而函數語句後面是確定不會帶 () 的,因此才報錯,因而聰明的人想出來,加上一對括號就能夠了。因而就變成了這樣:

(function jam() {}());

這樣就定義了一個函數同時也執行它,詳情參見 ECMAScript 的 Expression Statement 章節

相關文章
相關標籤/搜索