搞懂Javascript的call、apply、bind差異
前言
透過這篇將在框架源碼(如Vue.js)常常會用到的手法call、apply、bind做一些觀念的釐清,幫助自己在看源碼的時候可以更清楚框架作者的撰寫邏輯,也紀錄之前常搞錯觀念的例子提醒自己。
了解call、apply、bind用法
call
用法:函式名稱.call(綁定物件,參數,…)
返回值:函式執行後的結果apply
用法:函式名稱.apply(綁定物件,[參數,…])
返回值:函式執行後的結果bind (這邊不討論bind傳其他參數用法)
用法:函式名稱.bind(新的綁定物件)
返回值:函式本身
call和apply為函式呼叫
先來看以下範例
1 | function testFn(a, b, c) { |
上述結果都是印出1,2,3,testFn(1,2,3)
為一般函式呼叫方式,應該都很熟悉。
上述call和apply不綁定任何物件,所以塞了一個null,然後填上參數名(要留意apply的用法的參數是要用陣列[]裝起來),並執行,可以證明call和apply的確就是執行函式,然後返為執行後的結果。
bind為綁定物件
常見範例如下
1 | class Person { |
會發現印出結果是
會發現setTimeout裡面印出的this.name會是undefined,因為這個this是指向全域的Window物件,該如何解決呢?
我們這時候就能夠用bind,所以改為以下程式碼
1 | class Person { |
印出結果是
看起來好像有點亂、難理解,但其實仔細看,setTimeout函式的第一個參數就是函式,所以上述我們有介紹過bind用法,所以就是直接針對以下的函式:
1 | function() { |
上述這個函式後面加上 .bind(this)
,就完成bind(這個this就是指向這個Person物件,所以就能抓到Fred的值。相關觀念可看這篇文章)
(當然更直覺作法就是用ES6出現的箭頭函式,就能更直觀的解決,但這個不是我們今天要討論的重點,所以就先略過。)
bind用法觀念釐清
接下來的一個範例,是我以前在學習bind時發生的錯誤觀念產生的寫法
1 | const aaa = { |
本來以為多了一個bind(this)也應該沒事,但這個this其實是指向全域的Window物件,所以結果是印出undefined。
這個也幫助我釐清另一個觀念,物件裡面的this必須是包在傳統函式function(){}
裡面的this才是指向物件本身。
所以如果要印出this.x的值,把.bind(this)
刪掉即可
1 | const aaa = { |
就能印出123的值了
這時候,我們再將上述範例做一點延伸
綜合call、apply、bind綁定物件
1 | const aaa = { |
將 aaa.printX
指派給showX 函式,然後用一般函式的方式呼叫showX,因為這個函式的this就是指向全域的Window物件,所以第9行是印出undefined。
這時候這篇文章的三大主角都能處理這個問題,程式碼如下
1 | console.log(showX.call(aaa)); //123 |
三個方式都印出我們要的結果,我們綁定了aaa物件並執行它。
要特別留意,bind後要再多一個括號(),代表要去執行該函式。
小結
- call和apply都是執行函式,所以會return函式的結果;但bind就不同了,它只是單純綁定物件,return函式本身(也就是未執行)
- 如果call和apply不綁定物件就填上null即可,就是會跟一般函式的執行結果一樣。(但我們一般不太會去綁null,有點脫褲子放屁的感覺www)
- 這三個用法常常會跟this扯上關係,是用來改變原函式this的指向。