前言
javascript 中 call / apply / bind 這三個方法是日常工作中比較實用的改變函數 this 指向的方法,很友善,但由于涉及到對 this 的了解,同時也是很容易出錯的方法,也是面試官喜歡問的考題之一,今天就和大家一起好好聊一聊這三個方法
call / apply / bind 的差別
我們先簡單的過一下他們之間的異同點,有個簡單概念之後再詳細分析
共同點
他們的功能一緻,都是用來改變函數的 this 指向
// 文法
函數.call(thisArg, arg1, arg2, ...)
函數.apply(thisArg, [argsArray])
函數.bind(thisArg, [argsArray])
不同點
- call / apply 可以立即執行;bind 不會立即執行,而是傳回一個函數,可以在需要的時候再執行
- 參數不同:apply 第二個參數是數組;call 和 bind 有多個參數需要用逗号隔開挨個寫
簡單應用場景
- 改寫 this 指向,讓目标對象使用一些方法
const person = {
name: '江',
say: function () {
console.log(this.name)
}
}
person.say() // 江
const obj = {
name: '李'
}
person.say.call(obj) // 李
person.say.apply(obj) // 李
person.say.bind(obj)() // 李
- 借用方法
const numbers = [5, 458 , 120 , -215 ];
const maxInNumbers = Math.max.apply(Math, numbers), //458
maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
number 本身沒有 max 方法,但是 Math 有,我們就可以借助 call 或者 apply 使用其方法
手寫 call
Function.prototype.myCall = function (thisArg, ...argsArray) {
// 判斷 thisArg 是否 null / undefine
if (thisArg === null || thisArg === undefined) {
thisArg = window
}
console.log(this) // 這裡的 this 是調用的函數
thisArg.fn = this
thisArg.fn(...argsArray)
}
手寫 apply
Function.prototype.myApply = function (thisArg, argsArray) {
// 判斷 thisArg 是否 null / undefine
if (thisArg === null || thisArg === undefined) {
thisArg = window
}
console.log(this) // 這裡的 this 是調用的函數
thisArg.fn = this
thisArg.fn(...argsArray)
}
手寫 bind
Function.prototype.myBind = function (thisArg, argsArray) {
// 判斷 thisArg 是否 null / undefine
if (thisArg === null || thisArg === undefined) {
thisArg = window
}
console.log(this) // 這裡的 this 是調用的函數
thisArg.fn = this
return function () {
thisArg.fn(...argsArray)
}
}
使用一下我們手寫的三個方法,看是否滿足我們的要求
const person = {
name: '江',
say: function (word) {
console.log(this.name + ":" + word)
}
}
person.say('你好') // 江:你好
const obj = {
name: '李'
}
person.say.myCall(obj, 'hello') // 李:hello
person.say.myApply(obj, ['hello']) // 李:hello
person.say.myBind(obj, ['hello'])() // 李:hello
可以看到可以按期望輸出結果了~
總結
對于改寫 this 指向的實作,關鍵點是要了解:
- 我們自定義的方法,必須是挂載在 Function 這個個構造函數上的,這是一個大前提,因為隻有這樣,我們在任意方法上使用 . 才能找到我們的方法屬性(其實就是原型鍊)
- 還有就是對目前函數裡 this 的了解,這個場景可以簡單了解為 this 是誰調用就指向誰,那麼
- 函數.myCall() 就很清晰了,是函數調用了 myCall,是以 myCall 方法裡的 this 指向的就是函數
- 了解了以上兩點,就隻要把 this(想執行的方法) 挂到我們的目标對象thisArg上面就可以了