js中自己實現bind函數的方式


最近由於工作比較忙,好久都沒時間靜下心來研究一些東西了。今天在研究 call 和 apply 的區別的時候,看到 github 上面的一篇文章,看完以后,感覺啟發很大。

文章鏈接為 https://github.com/lin-xin/blog/issues/7 ,有興趣的童鞋可以前往學習一下。

但是我主要想寫的並不是我今天學習了這篇博文,那樣也就太沒有技術含量了對吧。

bind的實現
其實文章並不難理解,只要是對 js 有一定程度的了解的同學就能很容易看懂。我主要關心的是文章最后給出的自己動手實現 bind 函數的代碼,代碼如下:

if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var self = this, // 保存原函數
context = [].shift.call(arguments), // 保存需要綁定的this上下文
args = [].slice.call(arguments); // 剩余的參數轉為數組
return function () { // 返回一個新函數
self.apply(context,[].concat.call(args, [].slice.call(arguments)));
}
}
}
1
2
3
4
5
6
7
8
9
10
說實話,咋看這一段代碼,感覺好像很平淡無奇,但是你要是細細去體味的話,簡直能夠讓你回味無窮。

[].shift.call(arguments) 的含義
我相信,對於很多對 js 這門語言掌握並不算深的童鞋來說,這句代碼的含義貌似並不怎么容易理解,我們其實細分一下,可以拆分出好幾個知識點出來。

arguments 的含義
我相信,很多人應該知道 arguments,甚至於在實際的學習工作中也經常用到,但是我還是想在這個地方復習一下。

MDN 網站上對於 Arguments 對象是這么定義的:

arguments 是一個對應於傳遞給函數的參數的類數組對象。

我們可以看到 arguments 是一個類數組對象,他怎么用呢?別急,我們馬上給出示例:

 

上面是我在 chrome 瀏覽器控制台進行的一個小測試,可以看到,arguments 是一個類數組對象,它的值中包含了我們在調用函數時候,朝里面傳入的參數。

而我們知道,對於對象的調用,我們一般采用點號—— obj.key 的調用方式或者括號加對象的key的方式—— obj[key] ,這兩種方式是完全等同的。

因此我們在創建函數的時候,其實並不需要朝里面傳入變量,直接用 arguments 的方式來獲取變量豈不是更好?依我個人的拙見,雖然這種方式接收參數很靈活,但是也存在弊端,函數的參數本來就是寫起來讓自己或者他人閱讀起來更舒服的,君不見 C、Java 中參數還要指定類型呢,JS 已經夠寬松了,再按照這種標准寬松下去,寫起來是很隨意,但是用起來就很蛋疼了。

[].shift
咋一看,你還不一定看得懂這一句代碼,其實它就等同於 Array.prototype.shift ,不信你打開你的 chrome 瀏覽器控制台,試着輸入 Array.prototype.shift === [].shift ,然后你看看是不是能夠得到 true。也就是說,兩者其實是同一個函數,只是調用的方式存在差異,一個是通過原型的方式直接在類上調用;一個是通過實例化,繼承過來,然后再調用。

如果你對這個話題很感興趣,剛好我在知乎上看到有相關話題的討論:js中 [].slice 與 Array.prototype.slice 有什么區別?,你可以自行前往查看或者討論。

call
如果還有不知道call為何物的同學,請前往 mdn 網站 call 頁面去詳細了解。

mdn 對 call 的定義為:

call() 方法調用一個函數, 其具有一個指定的this值和分別地提供的參數(參數的列表)。

call 的語法為:

fun.call(thisArg, arg1, arg2, …)

在了解了上面三個小知識點以后,我們就能理解了 [].shift.call(arguments) 的含義了。arguments 是類數組對象,它沒有 shift 等數組獨有的方法,怎么辦?現在,我想要拿出傳入的參數中的第一個參數,怎么操作,就只有用這種方式了。

我們來試驗一下:

function a(){return arguments;}
var temp = a("a","b");

temp.slice(); //Uncaught TypeError: temp.slice is not a function
1
2
3
4
我們可以看到,如果你想直接對 temp 用 slice() 函數,不可能的,做不到的!因為 arguments 是類數組對象,並非真正的對象,並沒有 slice 方法。

但是如果你用下面方法:

[].slice.call(temp); //["a", "b"]
1
我們可以看到的是,用上面的方式,就神奇般的得到了想要的結果。

我們在類數組對象上面,成功的運用了數組的 slice 方法。也就是說,相當於,先將 temp 轉為數組,然后再對其運用了 slice 方法,然后返回了我們想要的結果。

當然,這種寫法,在 es6 面前,已經成為了過去式,es6 為數組新增了一個 from 方法,這個函數的使用方式如下:

Array.from(temp).slice(); //["a", "b"]
1
感興趣的同學,可以去 mdn 網站去了解 Array.from() 的詳細解釋。

因此,我們可以總結下,原來 context = [].shift.call(arguments) 這一句就是把參數中的第一個剪切出來,賦給 context,那么也就相當於起到了將 參數中的 this 保存的目的。

args = [].slice.call(arguments);
這一句,將除了 this 上下文的所有參數,傳給了 args ,以備后來使用。

bind的理解
要讀懂返回值,必須得理解,bind 的作用是什么。

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

有興趣的同學,可以前往 Function.prototype.bind() 頁面查看詳細解讀。簡單點理解,bind 就是用來綁定上下文的,強制將函數的執行環境綁定到目標作用域中去。與 call 和 apply 其實有點類似,但是不同點在於,它不會立即執行,而是返回一個函數。因此我們要想自己實現一個 bind 函數,就必須要返回一個函數,而且這個函數會接收綁定的參數的上下文。

返回函數
return function () { // 返回一個新函數
self.apply(context,[].concat.call(args, [].slice.call(arguments)));
}
1
2
3
這一段其實很好理解,因為bind 綁定了上下文,因此 self.apply 的第一個參數,是之前我們保存的 context。接下來,我們將 bind 的其余參數和調用bind后返回的函數在執行的過程中接收的參數進行拼接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM