JavaScript apply、call、bind 函数详解
生活随笔
收集整理的這篇文章主要介紹了
JavaScript apply、call、bind 函数详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
apply和call
apply和call非常類似,都是用于改變函數中this的指向,只是傳入的參數不同,等于間接調用一個函數,也等于將這個函數綁定到一個指定的對象上:
let name = 'window'
function getName(param1, param2) {
console.log(this.name)
console.log(param1, param2)
}
let obj = {
name: 'easylee',
}
getName.call(obj, 123, 23)
getName.apply(obj, [123, 23])
如上面的例子,如果直接調用 getName 那么返回的是 window ,但是通過 call 方法,將函數綁定到了 obj 上,成為obj的一個函數,同時里面的 this 也指向了obj
兩者主要的區別在于,當函數有多個參數時,call 是直接傳入多個參數,而 apply 將多個參數組合成一個數組傳輸參數
手寫call
原理:
- 首先,通過
Function.prototype.myCall將自定義的myCall方法添加到所有函數的原型對象上,使得所有函數實例都可以調用該方法。 - 在
myCall方法內部,首先通過typeof this !== "function"判斷調用myCall的對象是否為函數。如果不是函數,則拋出一個類型錯誤。 - 然后,判斷是否傳入了上下文對象
context。如果沒有傳入,則將context賦值為全局對象。這里使用了一種判斷全局對象的方法,先判斷是否存在global對象,如果存在則使用global,否則判斷是否存在window對象,如果存在則使用window,如果都不存在則將context賦值為undefined。 - 接下來,使用
Symbol創建一個唯一的鍵fn,用于將調用myCall的函數綁定到上下文對象的新屬性上。 - 將調用
myCall的函數賦值給上下文對象的fn屬性,實現了將函數綁定到上下文對象上的效果。 - 調用綁定在上下文對象上的函數,并傳入
myCall方法的其他參數args。 - 將綁定在上下文對象上的函數刪除,以避免對上下文對象造成影響。
- 返回函數調用的結果。
Function.prototype.myCall = function (context, ...args) {
// 判斷調用myCall的是否為函數
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall - 被調用的對象必須是函數')
}
// 判斷是否傳入上下文對象,不傳入則指定默認全局對象
context = context || (typeof global !== 'undefined' ? gloabl : typeof window !== 'undefined' ? window : undefined)
// 在上下文對象上綁定當前調用的函數,作為屬性方法
// 不能直接調用this方法函數,原因在于如果不將這個方法綁定到上下文對象上
// 直接執行this函數,this函數里面的this上下文對象無法識別為綁定的對象
let fn = Symbol('key')
context[fn] = this
const result = context[fn](...args)
// 刪除這個函數,避免對上下文對象造成影響
delete context[fn]
return result
}
const test = {
name: 'xxx',
hello: function () {
console.log(`hello,${this.name}!`)
},
add: function (a, b) {
return a + b
},
}
const obj = { name: 'world' }
test.hello.myCall(obj) //hello,world!
test.hello.call(obj) //hello,world!
console.log(test.add.myCall(null, 1, 2)) //3
console.log(test.add.call(null, 1, 2)) //3
手寫apply
Function.prototype.myApply = function (context, argsArr) {
// 判斷調用myApply的是否為函數
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myApply - 被調用的對象必須是函數");
}
// 判斷傳入的參數是否為數組
if (argsArr && !Array.isArray(argsArr)) {
throw new TypeError("Function.prototype.myApply - 第二個參數必須是數組");
}
// 如果沒有傳入上下文對象,則默認為全局對象
//global:nodejs的全局對象
//window:瀏覽器的全局對象
context =
context ||
(typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 用Symbol來創建唯一的fn,防止名字沖突
let fn = Symbol("key");
// this是調用myApply的函數,將函數綁定到上下文對象的新屬性上
context[fn] = this;
// 傳入myApply的多個參數
const result = Array.isArray(argsArr)
? context[fn](...argsArr)
: context[fn]();
// 將增加的fn方法刪除
delete context[fn];
return result;
};
// 測試一下
const test = {
name: "xxx",
hello: function () {
console.log(`hello,${this.name}!`);
},
};
const obj = { name: "world" };
test.hello.myApply(obj); //hello,world!
test.hello.apply(obj); //hello,world!
const arr = [2,3,6,5,1,7,9,5,0]
console.log(Math.max.myApply(null,arr));//9
console.log(Math.max.apply(null,arr));//9
bind
最后來看看 bind,和前面兩者主要的區別是,通過 bind 綁定的不會立即調用,而是返回一個新函數,然后需要手動調用這個新函數,來實現函數內部 this 的綁定
let name = 'window'
function getName(param1, param2) {
console.log(this.name)
console.log(param1)
console.log(param2)
}
let obj = {
name: 'easylee',
}
let fn = getName.bind(obj, 123, 234) // 通過綁定創建一個新函數,然后再調用新函數
fn()
除此之外, bind 還支持柯里化,也就是綁定時傳入的參數將保留到調用時直接使用
let sum = (x, y) => x + y
let succ = sum.bind(null, 1) // 綁定時沒有指定對象,但是給函數的第一個參數指定為1
succ(2) // 3, 調用時只傳遞了一個參數2,會直接對應到y,因為前面的1已經綁定到x上了
手寫bind
原理:
- 首先,通過
Function.prototype.myBind將自定義的myBind方法添加到所有函數的原型對象上,使得所有函數實例都可以調用該方法。 - 在
myBind方法內部,首先通過typeof this !== "function"判斷調用myBind的對象是否為函數。如果不是函數,則拋出一個類型錯誤。 - 然后,判斷是否傳入了上下文對象
context。如果沒有傳入,則將context賦值為全局對象。這里使用了一種判斷全局對象的方法,先判斷是否存在global對象,如果存在則使用global,否則判斷是否存在window對象,如果存在則使用window,如果都不存在則將context賦值為undefined。 - 保存原始函數的引用,使用
_this變量來表示。 - 返回一個新的閉包函數
fn作為綁定函數。這個函數接受任意數量的參數innerArgs。(關于閉包的介紹可以看這篇文章->閉包的應用場景) - 在返回的函數
fn中,首先判斷是否通過new關鍵字調用了函數。這里需要注意一點,如果返回出去的函數被當作構造函數使用,即使用new關鍵字調用時,this的值會指向新創建的實例對象。通過檢查this instanceof fn,可以判斷返回出去的函數是否被作為構造函數調用。這里使用new _this(...args, ...innerArgs)來創建新對象。 - 如果不是通過
new調用的,就使用apply方法將原始函數_this綁定到指定的上下文對象context上。這里使用apply方法的目的是將參數數組args.concat(innerArgs)作為參數傳遞給原始函數。
Function.prototype.myBind = function (context, ...args) {
// 判斷調用myBind的是否為函數
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myBind - 被調用的對象必須是函數");
}
// 如果沒有傳入上下文對象,則默認為全局對象
//global:nodejs的全局對象
//window:瀏覽器的全局對象
context =
context || (typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 保存原始函數的引用,this就是要綁定的函數
const _this = this;
// 返回一個新的函數作為綁定函數
return function fn(...innerArgs) {
// 判斷返回出去的函數有沒有被new
if (this instanceof fn) {
return new _this(...args, ...innerArgs);
}
// 使用apply方法將原函數綁定到指定的上下文對象上
return _this.apply(context,args.concat(innerArgs));
};
};
// 測試
const test = {
name: "xxx",
hello: function (a,b,c) {
console.log(`hello,${this.name}!`,a+b+c);
},
};
const obj = { name: "world" };
let hello1 = test.hello.myBind(obj,1);
let hello2 = test.hello.bind(obj,1);
hello1(2,3)//hello,world! 6
hello2(2,3)//hello,world! 6
console.log(new hello1(2,3));
//hello,undefined! 6
// hello {}
console.log(new hello2(2,3));
//hello,undefined! 6
// hello {}
總結一下,這三個函數都是用于改變函數內 this 對象的指向,只是使用方式有不同,其中 apply 傳遞多個參數使用數組的形式,call 則直接傳遞多個參數,而 bind 則可以將綁定時傳遞的參數保留到調用時直接使用,支持柯里化,同時 bind 不會直接調用,綁定之后返回一個新函數,然后通過調用新函數再執行。
總結
以上是生活随笔為你收集整理的JavaScript apply、call、bind 函数详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神经网络优化篇:梯度检验应用的注意事项(
- 下一篇: Go 语言能取代 Java,成为下一个