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的調用者,並將結果返回;
(完)
