apply call bind 相關
對於 apply call bind 之前也有寫過一篇很詳細的入門級文章 apply , call, bind 用法和區別
瞭解用法後,最好還是瞭解其實現原理,這樣才能掌握透徹
三者的區別
call
和apply
都是爲了解決改變this
的指向。作用都是相同的,只是傳參的方式不同- 除了第一個參數外,
call
可以接收一個參數列表,apply
只接受一個參數數組 bind
和其他兩個方法作用也是一致的,只是該方法會返回一個函數。並不會立刻執行
手寫 call
-
理一理需求:
- 改變 this 指向。默認指向 window
- 可以接收多個參數
- 改變 this 請求後執行該方法
- 所有的方法都掛載這個
myCall
-
理一下實現思路:
- 對於
this
。只需要記住,誰調用該方法,this 就指向誰(箭頭函數除外),讓傳入的對象/window 去調用方法即可 - 接收多參數:
arguments
或者 es6 新語法:...args
- 立刻執行函數,灑灑水了
- 所有的方法都掛載,那就用到原型鏈了,掛載在
Function
對象上
- 對於
-
萬事俱備,開始實現:
Function.prototype.myCall = function(context, ...args) {
// 默認的window對象
context = context && typeof context === 'object' ? context : window
// 防止覆蓋掉原有屬性
const key = Symbol()
// 這裏的this爲需要執行的方法
context[key] = this
// 方法執行
const result = context[key](...args)
delete context[key]
return result
}
- 總結一下實現
- 首先得確保新的指向是一個對象類型
object
。否則默認全都是 window。當然了 typeof null 也是等於 “object”。所以我們還得先用一個判斷確保 context 存在 Symbol
作用:防止屬性名的衝突。爲什麼要這麼做?因爲我們需要在新的對象上放一個屬性,這個屬性就是舊的this
。可怎麼保證新屬性不會和context
原有屬性衝突呢?那就要用到Symbol
- 可能對上面的
context[key] = this
疑惑。這到底是爲何,爲啥要在新的context
上放舊的 this 對象?後面會有代碼解釋原理 - 執行方法不存在什麼問題了~
- 最後要
delete
掉我們新增的屬性,畢竟不能影響原先的context
對象
- 首先得確保新的指向是一個對象類型
小拓展 context[key] = this 是爲何?
理解 context[key] = this
對整個手寫代碼實現有非常重要的意義
首先萬年不變的原理: 誰調用方法,this 就指向誰
那爲何不是context
去調用對應方法嗎?
因爲context
上根本就沒對應的方法
用一個小例子理解這個問題
let data = {
key: 1,
name: 'Jioho',
say: function() {
console.log(this.name)
}
}
function fn() {
console.log(this.name)
}
data.say() // Jioho
fn() // 打印爲空
// 那如果這樣:
data.sayfn = fn
data.sayfn() // Jioho
- 差距就出現了,只要對應方法在對象裏面,方法就能拿到當前的
this
實例 context[key] = this
同理。這時候的this
還是原函數的this
。原函數中的 this 纔有他本身的方法。所以我們直接把整個 this 賦值給context
。就類似於上面第 16 行代碼一樣- 至於我們一開始
key
爲什麼要Symbol
。就是怕重名如果我們第 16 新的屬性不叫sayfn
。叫say
,那麼我們後續的操作就會影響到原先的對象的屬性,這是絕對不能出現的問題!
手寫 apply
如果 手寫 call 理解後 apply 就只是一個傳參的問題
其餘代碼都無需改動,只需要在接收第二個參數的時候是一個數組類型即可
Function.prototype.myApply = function(context, args = []) {
// 默認的window對象
context = context && typeof context === 'object' ? context : window
// 防止覆蓋掉原有屬性
const key = Symbol()
// 這裏的this爲需要執行的方法
context[key] = this
// 方法執行
const result = context[key](...args)
delete context[key]
return result
}
手寫 bind
-
理一理需求:
- 改變 this 指向。默認指向 window
- 調用 bind 之前的參數,可以傳入多個
- 返回的函數中,還可以繼續傳參。這個場景還是很實用的
- 所有的方法都掛載這個
myBind
-
理一下實現思路:
- 改變 this 指向就無須多說了,不管是手寫實現,還是用原有的
apply
或者call
都行 - 調用 bind 之前可以傳遞多個參數,那還是用到
arguments
或者...args
統一接收了 - bind 需要返回一個
函數
。並且新的函數中還可以繼續傳參,那就要用到閉包了 - 所有的方法都掛載,那就用到原型鏈了,掛載在
Function
對象上
- 改變 this 指向就無須多說了,不管是手寫實現,還是用原有的
Function.prototype.myBind = function(context, ...args) {
context = context && typeof context === 'object' ? context : window
const _self = this
return function(...args2) {
return _self.apply(context, [...args, ...args2])
}
}
// 接下來驗證一下:
var data = { baseCount: 10 }
function add(num1, num2) {
return this.baseCount + num1 + num2
}
// 調用方法
var addBind = add.myBind(data, 1)
addBind(2) // 13
// 或者這樣寫
add.myBind(data, 1)(2) // 13
- 總結一下實現
- 萬年不變的
context
實現 - 由於有 2 次接收多個參數的方法,我們用到的
...args
接收的也必定是一個數組了。所以在返回的函數中用 apply 最合適不過了
- 萬年不變的
不要小看 bind 這看似奇怪的寫法,爲啥要分 2 次傳參?後續講到複雜的柯里化,就會發現 bind 現在的魅力所在
原文發佈於:apply call bind 相關 新的博客地址,感興趣的可以看看~