JavaScript 深入之bind的模擬實現

發表於2017-05-26

bind

一句話介紹 bind:

bind() 方法會建立一個新函式。當這個新函式被呼叫時,bind() 的第一個引數將作為它執行時的 this,之後的一序列引數將會在傳遞的實參前傳入作為它的引數。(來自於 MDN )

由此我們可以首先得出 bind 函式的兩個特點:

  1. 返回一個函式
  2. 可以傳入引數

返回函式的模擬實現

從第一個特點開始,我們舉個例子:

關於指定 this 的指向,我們可以使用 call 或者 apply 實現,關於 call 和 apply 的模擬實現,可以檢視《JavaScript深入之call和apply的模擬實現》。我們來寫第一版的程式碼:

傳參的模擬實現

接下來看第二點,可以傳入引數。這個就有點讓人費解了,我在 bind 的時候,是否可以傳參呢?我在執行 bind 返回的函式的時候,可不可以傳參呢?讓我們看個例子:

函式需要傳 name 和 age 兩個引數,竟然還可以在 bind 的時候,只傳一個 name,在執行返回的函式的時候,再傳另一個引數 age!

這可咋辦?不急,我們用 arguments 進行處理:

建構函式效果的模擬實現

完成了這兩點,最難的部分到啦!因為 bind 還有一個特點,就是

一個繫結函式也能使用new操作符建立物件:這種行為就像把原函式當成構造器。提供的 this 值被忽略,同時呼叫時的引數被提供給模擬函式。

也就是說當 bind 返回的函式作為建構函式的時候,bind 時指定的 this 值會失效,但傳入的引數依然生效。舉個例子:

注意:儘管在全域性和 foo 中都宣告瞭 value 值,最後依然返回了 undefind,說明繫結的 this 失效了,如果大家瞭解 new 的模擬實現,就會知道這個時候的 this 已經指向了 obj。

(哈哈,我這是為我的下一篇文章《JavaScript深入系列之new的模擬實現》打廣告)。

所以我們可以通過修改返回的函式的原型來實現,讓我們寫一下:

如果對原型鏈稍有困惑,可以檢視《JavaScript深入之從原型到原型鏈》

建構函式效果的優化實現

但是在這個寫法中,我們直接將 fbound.prototype = this.prototype,我們直接修改 fbound.prototype 的時候,也會直接修改函式的 prototype。這個時候,我們可以通過一個空函式來進行中轉:

到此為止,大的問題都已經解決,給自己一個贊!o( ̄▽ ̄)d

三個小問題

接下來處理些小問題:

1.apply 這段程式碼跟 MDN 上的稍有不同

在 MDN 中文版講 bind 的模擬實現時,apply 這裡的程式碼是:

多了一個關於 context 是否存在的判斷,然而這個是錯誤的!

舉個例子:

以上程式碼正常情況下會列印 2,如果換成了 context || this,這段程式碼就會列印 1!

所以這裡不應該進行 context 的判斷,大家檢視 MDN 同樣內容的英文版,就不存在這個判斷!

2.呼叫 bind 的不是函式咋辦?

不行,我們要報錯!

3.我要線上上用

那別忘了做個相容:

當然最好是用es5-shim啦。

最終程式碼

所以最最後的程式碼就是:

深入系列

JavaScript深入系列目錄地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列預計寫十五篇左右,旨在幫大家捋順JavaScript底層知識,重點講解如原型、作用域、執行上下文、變數物件、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎star,對作者也是一種鼓勵。

本系列:

  1. JavaScirpt 深入之從原型到原型鏈
  2. JavaScript 深入之詞法作用域和動態作用域
  3. JavaScript 深入之執行上下文棧
  4. JavaScript 深入之變數物件
  5. JavaScript 深入之作用域鏈
  6. JavaScript 深入之從 ECMAScript 規範解讀 this
  7. JavaScript 深入之執行上下文
  8. JavaScript 深入之閉包
  9. JavaScript 深入之引數按值傳遞
  10. JavaScript 深入之call和apply的模擬實現

相關文章