1. 前言
使用原生JS
實現call
和apply
函數,充分了解其內部原理。call
和apply
都是為了解決改變this
的指向。作用都相同,只是傳參的方式不同。除了第一個參數外,call
可以接受一個參數列表,apply
只接受一個參數數組。
2. call函數
2.1 描述
call()
方法使用一個指定的 this
值和單獨給出的一個或多個參數來調用一個函數。
2.2 語法
fun.call(thisArg, arg1, arg2, ...)
2.3 參數
- thisArg:可選的。在 fun 函數運行時指定的
this
值。需要注意的是,指定的this
值並不一定是該函數執行時真正的this
值,如果這個函數在非嚴格模式
下運行,則指定為null
和undefined
的this
值會自動指向全局對象(瀏覽器中就是 window 對象),同時值為原始值(數字,字符串,布爾值)的this
會指向該原始值的自動包裝對象。 - arg1, arg2, ...:可選的。指定的參數列表。
2.4 返回值
使用調用者提供的 this
值和參數調用該函數的返回值。若該方法沒有返回值,則返回 undefined
。
2.5 實現
Function.prototype.myCall = function (context){
if (typeof this !== 'function') {
throw new TypeError('Error')
}
if (context === null || context === undefined) {
context = window // 指定為 null 和 undefined 的 this 值會自動指向全局對象(瀏覽器中為window)
} else {
context = Object(context) // 值為原始值(數字,字符串,布爾值)的 this 會指向該原始值的實例對象
}
context.fn = this
//通過參數偽數組將context后面的參數取出來
let args = [...arguments].slice(1)
let result = context.fn(...args)
//刪除 fn
delete context.fn
return result
}
實現思路:
- 首先,判斷調用
mycall
的是不是函數,如果不是,則直接拋出異常; - 接着,判斷是否傳入了第一個參數
context
,也就是要指定的this
值,如果沒有傳入,則默認為window
全局對象; - 然后,誰將來調用
mycall
,那么this
就是誰,將其賦給context.fn
; - 然后,通過參數偽數組將
context
后面的參數取出來,並傳給context.fn
獲得執行結果result
; - 最后,刪除掉
context.fn
,並將result
返回;
3. aplly函數
3.1 描述
apply() 方法調用一個具有給定this
值的函數,以及作為一個數組(或類似數組對象)提供的參數。
3.2 語法
func.apply(thisArg, [argsArray])
3.3 參數
- thisArg:可選的。在 func 函數運行時使用的
this
值。請注意,this
可能不是該方法看到的實際值:如果這個函數處於非嚴格模式下,則指定為null
或undefined
時會自動替換為指向全局對象,原始值會被包裝。 - argsArray:可選的。一個數組或者類數組對象,其中的數組元素將作為單獨的參數傳給
func
函數。如果該參數的值為null
或undefined
,則表示不需要傳入任何參數。從ECMAScript 5 開始可以使用類數組對象。
3.4 返回值
調用有指定**this**
值和參數的函數的結果。
3.5 實現
Function.prototype.myApply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
if (context === null || context === undefined) {
context = window // 指定為 null 和 undefined 的 this 值會自動指向全局對象(瀏覽器中為window)
} else {
context = Object(context) // 值為原始值(數字,字符串,布爾值)的 this 會指向該原始值的實例對象
}
context.fn = this
let result
//判斷是否存在第二個參數
//如果存在就將第二個參數也展開
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
實現思路:
- 首先,判斷調用
myapply
的是不是函數,如果不是,則直接拋出異常; - 接着,判斷是否傳入了第一個參數
context
,也就是要指定的this
值,如果沒有傳入,則默認為window
全局對象; - 然后,誰將來調用
myapply
,那么this
就是誰,將其賦給context.fn
; - 然后,判斷是否傳入了第二個參數,如果傳入了則將其使用展開運算符
...
傳給context.fn
獲得執行結果result
,如果沒有傳入,則直接調用context.fn
獲得執行結果result
; - 最后,刪除掉
context.fn
,並將result
返回;
4. bind函數
4.1 描述
bind()
方法創建一個新的函數,在bind()
被調用時,這個新函數的this
被bind
的第一個參數指定,其余的參數將作為新函數的參數供調用時使用。
4.2 語法
func.bind(thisArg[, arg1[, arg2[, ...]]])
4.3 參數
-
thisArg:調用綁定函數時作為
this
參數傳遞給目標函數的值。 如果使用new
運算符構造綁定函數,則忽略該值。當使用bind
在setTimeout
中創建一個函數(作為回調提供)時,作為thisArg
傳遞的任何原始值都將轉換為object
。如果bind
函數的參數列表為空,執行作用域的this
將被視為新函數的thisArg
。 -
arg1, arg2, ...:當目標函數被調用時,預先添加到綁定函數的參數列表中的參數。
4.4 返回值
返回一個原函數的拷貝,並擁有指定的this值和初始參數。
4.5 實現
Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
const args = [...arguments].slice(1)
//返回一個函數
return function F () {
if (this instanceof F) { // this是否是F的實例 也就是返回的F是否通過new調用
return new _this(...args, ...arguments)
}
return _this.apply(context,args.concat(...arguments))
}
}
實現思路:
- 首先,判斷調用
mybind
的是不是函數,如果不是,則直接拋出異常; - 接着,誰將來調用
mybind
,那么this
就是誰,將其賦給_this
,緩存一下; - 然后,通過參數偽數組將
context
后面的參數(預先添加到綁定函數的參數)取出來,記作args
, - 然后,返回一個函數,並判斷如果使用
new
運算符構造綁定函數,則忽略傳入的第一個參數context
,並將預先添加到綁定函數的參數args
和將來傳入新函數的參數arguments
分別通過展開運算符...
依次傳入給調用mybind
的調用者,並將結果返回。 - 最后,如果不是使用
new
運算符構造綁定函數,則對調用者使用apply
方法,將傳入的第一個參數以及預先添加到綁定函數的參數args
和將來傳入新函數的參數arguments
分別通過展開運算符...
依次傳入給調用mybind
的調用者,並將結果返回;
(完)