玩轉(zhuǎn)JavaScript函數(shù):apply/call/bind技巧
apply和call
apply和call非常類似,都是用于改變函數(shù)中this的指向,只是傳入的參數(shù)不同,等于間接調(diào)用一個函數(shù),也等于將這個函數(shù)綁定到一個指定的對象上:
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])
如上面的例子,如果直接調(diào)用 getName 那么返回的是 window ,但是通過 call 方法,將函數(shù)綁定到了 obj 上,成為obj的一個函數(shù),同時里面的 this 也指向了obj
兩者主要的區(qū)別在于,當函數(shù)有多個參數(shù)時,call 是直接傳入多個參數(shù),而 apply 將多個參數(shù)組合成一個數(shù)組傳輸參數(shù)
手寫call
原理:
- 首先,通過
Function.prototype.myCall將自定義的myCall方法添加到所有函數(shù)的原型對象上,使得所有函數(shù)實例都可以調(diào)用該方法。 - 在
myCall方法內(nèi)部,首先通過typeof this !== "function"判斷調(diào)用myCall的對象是否為函數(shù)。如果不是函數(shù),則拋出一個類型錯誤。 - 然后,判斷是否傳入了上下文對象
context。如果沒有傳入,則將context賦值為全局對象。這里使用了一種判斷全局對象的方法,先判斷是否存在global對象,如果存在則使用global,否則判斷是否存在window對象,如果存在則使用window,如果都不存在則將context賦值為undefined。 - 接下來,使用
Symbol創(chuàng)建一個唯一的鍵fn,用于將調(diào)用myCall的函數(shù)綁定到上下文對象的新屬性上。 - 將調(diào)用
myCall的函數(shù)賦值給上下文對象的fn屬性,實現(xiàn)了將函數(shù)綁定到上下文對象上的效果。 - 調(diào)用綁定在上下文對象上的函數(shù),并傳入
myCall方法的其他參數(shù)args。 - 將綁定在上下文對象上的函數(shù)刪除,以避免對上下文對象造成影響。
- 返回函數(shù)調(diào)用的結果。
Function.prototype.myCall = function (context, ...args) {
// 判斷調(diào)用myCall的是否為函數(shù)
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall - 被調(diào)用的對象必須是函數(shù)')
}
// 判斷是否傳入上下文對象,不傳入則指定默認全局對象
context = context || (typeof global !== 'undefined' ? gloabl : typeof window !== 'undefined' ? window : undefined)
// 在上下文對象上綁定當前調(diào)用的函數(shù),作為屬性方法
// 不能直接調(diào)用this方法函數(shù),原因在于如果不將這個方法綁定到上下文對象上
// 直接執(zhí)行this函數(shù),this函數(shù)里面的this上下文對象無法識別為綁定的對象
let fn = Symbol('key')
context[fn] = this
const result = context[fn](...args)
// 刪除這個函數(shù),避免對上下文對象造成影響
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) {
// 判斷調(diào)用myApply的是否為函數(shù)
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myApply - 被調(diào)用的對象必須是函數(shù)");
}
// 判斷傳入的參數(shù)是否為數(shù)組
if (argsArr && !Array.isArray(argsArr)) {
throw new TypeError("Function.prototype.myApply - 第二個參數(shù)必須是數(shù)組");
}
// 如果沒有傳入上下文對象,則默認為全局對象
//global:nodejs的全局對象
//window:瀏覽器的全局對象
context =
context ||
(typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 用Symbol來創(chuàng)建唯一的fn,防止名字沖突
let fn = Symbol("key");
// this是調(diào)用myApply的函數(shù),將函數(shù)綁定到上下文對象的新屬性上
context[fn] = this;
// 傳入myApply的多個參數(shù)
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,和前面兩者主要的區(qū)別是,通過 bind 綁定的不會立即調(diào)用,而是返回一個新函數(shù),然后需要手動調(diào)用這個新函數(shù),來實現(xiàn)函數(shù)內(nèi)部 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) // 通過綁定創(chuàng)建一個新函數(shù),然后再調(diào)用新函數(shù)
fn()
除此之外, bind 還支持柯里化,也就是綁定時傳入的參數(shù)將保留到調(diào)用時直接使用
let sum = (x, y) => x + y let succ = sum.bind(null, 1) // 綁定時沒有指定對象,但是給函數(shù)的第一個參數(shù)指定為1 succ(2) // 3, 調(diào)用時只傳遞了一個參數(shù)2,會直接對應到y(tǒng),因為前面的1已經(jīng)綁定到x上了
手寫bind
原理:
- 首先,通過
Function.prototype.myBind將自定義的myBind方法添加到所有函數(shù)的原型對象上,使得所有函數(shù)實例都可以調(diào)用該方法。 - 在
myBind方法內(nèi)部,首先通過typeof this !== "function"判斷調(diào)用myBind的對象是否為函數(shù)。如果不是函數(shù),則拋出一個類型錯誤。 - 然后,判斷是否傳入了上下文對象
context。如果沒有傳入,則將context賦值為全局對象。這里使用了一種判斷全局對象的方法,先判斷是否存在global對象,如果存在則使用global,否則判斷是否存在window對象,如果存在則使用window,如果都不存在則將context賦值為undefined。 - 保存原始函數(shù)的引用,使用
_this變量來表示。 - 返回一個新的閉包函數(shù)
fn作為綁定函數(shù)。這個函數(shù)接受任意數(shù)量的參數(shù)innerArgs。(關于閉包的介紹可以看這篇文章->閉包的應用場景) - 在返回的函數(shù)
fn中,首先判斷是否通過new關鍵字調(diào)用了函數(shù)。這里需要注意一點,如果返回出去的函數(shù)被當作構造函數(shù)使用,即使用new關鍵字調(diào)用時,this的值會指向新創(chuàng)建的實例對象。通過檢查this instanceof fn,可以判斷返回出去的函數(shù)是否被作為構造函數(shù)調(diào)用。這里使用new _this(...args, ...innerArgs)來創(chuàng)建新對象。 - 如果不是通過
new調(diào)用的,就使用apply方法將原始函數(shù)_this綁定到指定的上下文對象context上。這里使用apply方法的目的是將參數(shù)數(shù)組args.concat(innerArgs)作為參數(shù)傳遞給原始函數(shù)。
Function.prototype.myBind = function (context, ...args) {
// 判斷調(diào)用myBind的是否為函數(shù)
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myBind - 被調(diào)用的對象必須是函數(shù)");
}
// 如果沒有傳入上下文對象,則默認為全局對象
//global:nodejs的全局對象
//window:瀏覽器的全局對象
context =
context || (typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 保存原始函數(shù)的引用,this就是要綁定的函數(shù)
const _this = this;
// 返回一個新的函數(shù)作為綁定函數(shù)
return function fn(...innerArgs) {
// 判斷返回出去的函數(shù)有沒有被new
if (this instanceof fn) {
return new _this(...args, ...innerArgs);
}
// 使用apply方法將原函數(shù)綁定到指定的上下文對象上
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 {}
總結一下,這三個函數(shù)都是用于改變函數(shù)內(nèi) this 對象的指向,只是使用方式有不同,其中 apply 傳遞多個參數(shù)使用數(shù)組的形式,call 則直接傳遞多個參數(shù),而 bind 則可以將綁定時傳遞的參數(shù)保留到調(diào)用時直接使用,支持柯里化,同時 bind 不會直接調(diào)用,綁定之后返回一個新函數(shù),然后通過調(diào)用新函數(shù)再執(zhí)行。
到此這篇關于玩轉(zhuǎn)JavaScript函數(shù):apply/call/bind技巧的文章就介紹到這了,更多相關JavaScript apply、call、bind 函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- JavaScript函數(shù)的4種調(diào)用方法詳解
- JavaScript函數(shù)的調(diào)用以及參數(shù)傳遞
- 深入理解JavaScript函數(shù)參數(shù)(推薦)
- 超鏈接怎么正確調(diào)用javascript函數(shù)
- 改變javascript函數(shù)內(nèi)部this指針指向的三種方法
- 從JQuery源碼分析JavaScript函數(shù)的apply方法與call方法
- 用apply讓javascript函數(shù)僅執(zhí)行一次的代碼
- JavaScript函數(shù)之call、apply以及bind方法案例詳解
- JavaScript函數(shù)apply()和call()用法與異同分析
- 詳解JavaScript函數(shù)callee、call、apply的區(qū)別
相關文章
javaScript canvas實現(xiàn)(畫筆大小 顏色 橡皮的實例)
下面小編就為大家分享一篇javaScript canvas實現(xiàn)(畫筆大小 顏色 橡皮的實例),具有很好的參考價值,希望對大家有所幫助2017-11-11
js前端實現(xiàn)圖片懶加載(lazyload)的兩種方式
本篇文章主要介紹了js前端實現(xiàn)圖片懶加載(lazyload)的兩種方式 ,使用圖片懶加載可以提高網(wǎng)頁運行速度,有興趣的可以了解一下。2017-04-04
《javascript設計模式》學習筆記三:Javascript面向?qū)ο蟪绦蛟O計單例模式原理與實現(xiàn)方法分析
這篇文章主要介紹了Javascript面向?qū)ο蟪绦蛟O計單例模式原理與實現(xiàn)方法,結合實例形式分析了《javascript設計模式》中Javascript面向?qū)ο髥卫J较嚓P概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04
webpack3里使用uglifyjs壓縮js時打包報錯的解決
這篇文章主要介紹了webpack3里使用uglifyjs壓縮js時打包報錯的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12

