实现一个bind函数
目前的打算還是繼續深入前端基礎知識,所以打算從polyfill開始做起。
bind函數
bind函數最常見的用法是綁定函數的上下文,比如在setTimeout中的this一般都是指向window,如果我們想改變上下文,這里可以使用bind函數來實現。
var a = 10; var test = function() {console.log(this.a); } // 如果直接執行test,最終打印的是10. var bindTest = test.bind({a: "111"}) bindTest(); // 111 復制代碼從上面這個例子可以看出來,bind函數改變了test函數中this的指向。 除此之外,bind函數還有兩個特殊的用法,一個是柯里化,一個是綁定構造函數無效。
柯里化
bind函數的柯里化其實是不完全的,其實只做了一次柯里化,看過MDN的polyfill實現后也就理解了。
var test = function(b) {return this.a + b; } // 如果直接執行test,最終打印的是10. var bindTest1 = test.bind({a: 20}); bindTest1(10); // 30 // 這里的bind是個柯里化的函數 var bindTest2 = test.bind({a: 20}, 10); bindTest2(); // 30; 復制代碼構造函數無效
其實準確的來說,bind并不是對構造函數無效,只是對new的時候無效,如果直接執行構造函數,那么還是有效的。
var a = 10; var Test = function(a) {console.log(this.a); } var bindTest = Test.bind({a: 20}); bindTest(); // 20 // 在new的時候,Test中的this并沒有指向bind中的對象 new bindTest(); // undefined 復制代碼實現一個bind
我們可以先實現一個簡易版本的bind,再不斷完善。由于是在函數上調用bind,所以bind方法肯定存在于Function.prototype上面,其次bind函數要有改變上下文的作用,我們想一想,怎么才能改變上下文?沒錯,就是call和apply方法。
然后還要可以柯里化,還好這里只是簡單的柯里化,我們只要在bind中返回一個新的函數,并且將前后兩次的參數收集起來就可以做到了。
Function.prototype.bind = function() {var args = arguments;// 獲取到新的上下文var context = args[0];// 保存當前的函數var func = this;// 獲取其他的參數var thisArgs = Array.prototype.slice.call(args, 1);var returnFunc = function() {// 將兩次獲取到的參數合并Array.prototype.push.apply(thisArgs, arguments)// 使用apply改變上下文return func.apply(context, thisArgs);}return returnFunc; } 復制代碼這里實現了一個簡單的bind函數,可以支持簡單的柯里化,也可以改變上下文作用域,但是在new一個構造函數的時候還是會改變上下文。
這里我們需要考慮一下,怎么做才能讓在new的時候無效,而其他時候有效?
所以我們需要在returnFunc里面的apply第一個參數進行判斷,如果是用new調用構造函數的時候應該傳入函數本身,否則才應該傳入context,那么該怎么判斷是new調用呢?
關于在new一個構造函數的時候,這中間做了什么,建議參考這個問題:在js里面當new了一個對象時,這中間發生了什么?
所以我們很容易得出,由于最終返回的是returnFunc,所以最終是new的這個函數,而在new的過程中,會執行一遍這個函數,所以這個過程中returnFunc里面的this指向new的時候創建的那個對象,而那個新對象指向returnFunc函數。
但是我們希望調用后的結果只是new的func函數,和我們正常new func一樣,所以這里猜想,在returnFunc中,一定會將其this傳入func函數中執行,這樣才能滿足這幾個條件。
Function.prototype.bind = function() {var args = arguments || [];var context = args[0];var func = this;var thisArgs = Array.prototype.slice.call(args, 1);var returnFunc = function() {Array.prototype.push.apply(thisArgs, arguments);// 最關鍵的一步,this是new returnFunc中創建的那個新對象,此時將其傳給func函數,其實相當于做了new操作最后一步(執行構造函數)return func.apply(this instanceof returnFunc ? this : context, thisArgs);}return returnFunc } function foo(c) {this.b = 100;console.log(c);return this.a; }var func = foo.bind({a:1}); var newFunc = new func() // undefined 復制代碼但是這樣還是不夠的,如果foo函數原型上面還有更多的方法和屬性,這里的newFunc是沒法獲取到的,因為foo.prototype不在newFunc的原型鏈上面。 所以這里我們需要做一些改動,由于傳入apply的是returnFunc的一個實例(this),所以我們應該讓returnFunc繼承func函數,最終版是這樣的。
Function.prototype.bind = function() {var args = arguments || [];var context = args[0];var func = this;var thisArgs = Array.prototype.slice.call(args, 1);var returnFunc = function() {Array.prototype.push.apply(thisArgs, arguments);// 最關鍵的一步,this是new returnFunc中創建的那個新對象,此時將其傳給func函數,其實相當于做了new操作最后一步(執行構造函數)return func.apply(this instanceof func ? this : context, thisArgs);}returnFunc.prototype = new func()return returnFunc } 復制代碼這樣我們就完成了一個bind函數,這與MDN上面的polyfill實現方式大同小異,這里可以參考一下MDN的實現:Function.prototype.bind()
參考鏈接:
MDN:Function.prototype.bind()
手寫bind()函數,理解MDN上的標準Polyfill
轉載于:https://juejin.im/post/5c0cdcb9e51d4504a02eb5bb
總結
以上是生活随笔為你收集整理的实现一个bind函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 003—索引
- 下一篇: 《数据挖掘与数据化运营实战 思路、方法、