玩轉(zhuǎn)JavaScript函數(shù):apply/call/bind技巧
apply和call
apply和call非常類(lèi)似,都是用于改變函數(shù)中this的指向,只是傳入的參數(shù)不同,等于間接調(diào)用一個(gè)函數(shù),也等于將這個(gè)函數(shù)綁定到一個(gè)指定的對(duì)象上:
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
,但是通過(guò) call
方法,將函數(shù)綁定到了 obj
上,成為obj的一個(gè)函數(shù),同時(shí)里面的 this
也指向了obj
兩者主要的區(qū)別在于,當(dāng)函數(shù)有多個(gè)參數(shù)時(shí),call
是直接傳入多個(gè)參數(shù),而 apply
將多個(gè)參數(shù)組合成一個(gè)數(shù)組傳輸參數(shù)
手寫(xiě)call
原理:
- 首先,通過(guò)
Function.prototype.myCall
將自定義的myCall
方法添加到所有函數(shù)的原型對(duì)象上,使得所有函數(shù)實(shí)例都可以調(diào)用該方法。 - 在
myCall
方法內(nèi)部,首先通過(guò)typeof this !== "function"
判斷調(diào)用myCall
的對(duì)象是否為函數(shù)。如果不是函數(shù),則拋出一個(gè)類(lèi)型錯(cuò)誤。 - 然后,判斷是否傳入了上下文對(duì)象
context
。如果沒(méi)有傳入,則將context
賦值為全局對(duì)象。這里使用了一種判斷全局對(duì)象的方法,先判斷是否存在global
對(duì)象,如果存在則使用global
,否則判斷是否存在window
對(duì)象,如果存在則使用window
,如果都不存在則將context
賦值為undefined
。 - 接下來(lái),使用
Symbol
創(chuàng)建一個(gè)唯一的鍵fn
,用于將調(diào)用myCall
的函數(shù)綁定到上下文對(duì)象的新屬性上。 - 將調(diào)用
myCall
的函數(shù)賦值給上下文對(duì)象的fn
屬性,實(shí)現(xiàn)了將函數(shù)綁定到上下文對(duì)象上的效果。 - 調(diào)用綁定在上下文對(duì)象上的函數(shù),并傳入
myCall
方法的其他參數(shù)args
。 - 將綁定在上下文對(duì)象上的函數(shù)刪除,以避免對(duì)上下文對(duì)象造成影響。
- 返回函數(shù)調(diào)用的結(jié)果。
Function.prototype.myCall = function (context, ...args) { // 判斷調(diào)用myCall的是否為函數(shù) if (typeof this !== 'function') { throw new TypeError('Function.prototype.myCall - 被調(diào)用的對(duì)象必須是函數(shù)') } // 判斷是否傳入上下文對(duì)象,不傳入則指定默認(rèn)全局對(duì)象 context = context || (typeof global !== 'undefined' ? gloabl : typeof window !== 'undefined' ? window : undefined) // 在上下文對(duì)象上綁定當(dāng)前調(diào)用的函數(shù),作為屬性方法 // 不能直接調(diào)用this方法函數(shù),原因在于如果不將這個(gè)方法綁定到上下文對(duì)象上 // 直接執(zhí)行this函數(shù),this函數(shù)里面的this上下文對(duì)象無(wú)法識(shí)別為綁定的對(duì)象 let fn = Symbol('key') context[fn] = this const result = context[fn](...args) // 刪除這個(gè)函數(shù),避免對(duì)上下文對(duì)象造成影響 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
手寫(xiě)apply
Function.prototype.myApply = function (context, argsArr) { // 判斷調(diào)用myApply的是否為函數(shù) if (typeof this !== "function") { throw new TypeError("Function.prototype.myApply - 被調(diào)用的對(duì)象必須是函數(shù)"); } // 判斷傳入的參數(shù)是否為數(shù)組 if (argsArr && !Array.isArray(argsArr)) { throw new TypeError("Function.prototype.myApply - 第二個(gè)參數(shù)必須是數(shù)組"); } // 如果沒(méi)有傳入上下文對(duì)象,則默認(rèn)為全局對(duì)象 //global:nodejs的全局對(duì)象 //window:瀏覽器的全局對(duì)象 context = context || (typeof global !== "undefined" ? global : typeof window !== "undefined" ? window : undefined); // 用Symbol來(lái)創(chuàng)建唯一的fn,防止名字沖突 let fn = Symbol("key"); // this是調(diào)用myApply的函數(shù),將函數(shù)綁定到上下文對(duì)象的新屬性上 context[fn] = this; // 傳入myApply的多個(gè)參數(shù) const result = Array.isArray(argsArr) ? context[fn](...argsArr) : context[fn](); // 將增加的fn方法刪除 delete context[fn]; return result; }; // 測(cè)試一下 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
最后來(lái)看看 bind
,和前面兩者主要的區(qū)別是,通過(guò) bind 綁定的不會(huì)立即調(diào)用,而是返回一個(gè)新函數(shù),然后需要手動(dòng)調(diào)用這個(gè)新函數(shù),來(lái)實(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) // 通過(guò)綁定創(chuàng)建一個(gè)新函數(shù),然后再調(diào)用新函數(shù) fn()
除此之外, bind
還支持柯里化,也就是綁定時(shí)傳入的參數(shù)將保留到調(diào)用時(shí)直接使用
let sum = (x, y) => x + y let succ = sum.bind(null, 1) // 綁定時(shí)沒(méi)有指定對(duì)象,但是給函數(shù)的第一個(gè)參數(shù)指定為1 succ(2) // 3, 調(diào)用時(shí)只傳遞了一個(gè)參數(shù)2,會(huì)直接對(duì)應(yīng)到y(tǒng),因?yàn)榍懊娴?已經(jīng)綁定到x上了
手寫(xiě)bind
原理:
- 首先,通過(guò)
Function.prototype.myBind
將自定義的myBind
方法添加到所有函數(shù)的原型對(duì)象上,使得所有函數(shù)實(shí)例都可以調(diào)用該方法。 - 在
myBind
方法內(nèi)部,首先通過(guò)typeof this !== "function"
判斷調(diào)用myBind
的對(duì)象是否為函數(shù)。如果不是函數(shù),則拋出一個(gè)類(lèi)型錯(cuò)誤。 - 然后,判斷是否傳入了上下文對(duì)象
context
。如果沒(méi)有傳入,則將context
賦值為全局對(duì)象。這里使用了一種判斷全局對(duì)象的方法,先判斷是否存在global
對(duì)象,如果存在則使用global
,否則判斷是否存在window
對(duì)象,如果存在則使用window
,如果都不存在則將context
賦值為undefined
。 - 保存原始函數(shù)的引用,使用
_this
變量來(lái)表示。 - 返回一個(gè)新的閉包函數(shù)
fn
作為綁定函數(shù)。這個(gè)函數(shù)接受任意數(shù)量的參數(shù)innerArgs
。(關(guān)于閉包的介紹可以看這篇文章->閉包的應(yīng)用場(chǎng)景) - 在返回的函數(shù)
fn
中,首先判斷是否通過(guò)new
關(guān)鍵字調(diào)用了函數(shù)。這里需要注意一點(diǎn),如果返回出去的函數(shù)被當(dāng)作構(gòu)造函數(shù)使用,即使用new
關(guān)鍵字調(diào)用時(shí),this
的值會(huì)指向新創(chuàng)建的實(shí)例對(duì)象。通過(guò)檢查this instanceof fn
,可以判斷返回出去的函數(shù)是否被作為構(gòu)造函數(shù)調(diào)用。這里使用new _this(...args, ...innerArgs)
來(lái)創(chuàng)建新對(duì)象。 - 如果不是通過(guò)
new
調(diào)用的,就使用apply
方法將原始函數(shù)_this
綁定到指定的上下文對(duì)象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)用的對(duì)象必須是函數(shù)"); } // 如果沒(méi)有傳入上下文對(duì)象,則默認(rèn)為全局對(duì)象 //global:nodejs的全局對(duì)象 //window:瀏覽器的全局對(duì)象 context = context || (typeof global !== "undefined" ? global : typeof window !== "undefined" ? window : undefined); // 保存原始函數(shù)的引用,this就是要綁定的函數(shù) const _this = this; // 返回一個(gè)新的函數(shù)作為綁定函數(shù) return function fn(...innerArgs) { // 判斷返回出去的函數(shù)有沒(méi)有被new if (this instanceof fn) { return new _this(...args, ...innerArgs); } // 使用apply方法將原函數(shù)綁定到指定的上下文對(duì)象上 return _this.apply(context,args.concat(innerArgs)); }; }; // 測(cè)試 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 {}
總結(jié)一下,這三個(gè)函數(shù)都是用于改變函數(shù)內(nèi) this
對(duì)象的指向,只是使用方式有不同,其中 apply 傳遞多個(gè)參數(shù)使用數(shù)組的形式,call 則直接傳遞多個(gè)參數(shù),而 bind 則可以將綁定時(shí)傳遞的參數(shù)保留到調(diào)用時(shí)直接使用,支持柯里化,同時(shí) bind 不會(huì)直接調(diào)用,綁定之后返回一個(gè)新函數(shù),然后通過(guò)調(diào)用新函數(shù)再執(zhí)行。
到此這篇關(guān)于玩轉(zhuǎn)JavaScript函數(shù):apply/call/bind技巧的文章就介紹到這了,更多相關(guān)JavaScript apply、call、bind 函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 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ū)別
相關(guān)文章
javaScript canvas實(shí)現(xiàn)(畫(huà)筆大小 顏色 橡皮的實(shí)例)
下面小編就為大家分享一篇javaScript canvas實(shí)現(xiàn)(畫(huà)筆大小 顏色 橡皮的實(shí)例),具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11使用JavaScript執(zhí)行表單驗(yàn)證的方法
在Web前端開(kāi)發(fā)中,表單驗(yàn)證是一個(gè)常見(jiàn)的任務(wù),用于確保用戶輸入的數(shù)據(jù)符合預(yù)期的要求,通過(guò)使用JavaScript,可以實(shí)現(xiàn)強(qiáng)大的表單驗(yàn)證功能,提升用戶體驗(yàn)和數(shù)據(jù)的可靠性,本文將詳細(xì)介紹如何使用JavaScript執(zhí)行表單驗(yàn)證,包括基本概念、作用說(shuō)明、實(shí)現(xiàn)方法和實(shí)際開(kāi)發(fā)中的使用技巧2024-11-11js前端實(shí)現(xiàn)圖片懶加載(lazyload)的兩種方式
本篇文章主要介紹了js前端實(shí)現(xiàn)圖片懶加載(lazyload)的兩種方式 ,使用圖片懶加載可以提高網(wǎng)頁(yè)運(yùn)行速度,有興趣的可以了解一下。2017-04-04《javascript設(shè)計(jì)模式》學(xué)習(xí)筆記三:Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)單例模式原理與實(shí)現(xiàn)方法分析
這篇文章主要介紹了Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)單例模式原理與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了《javascript設(shè)計(jì)模式》中Javascript面向?qū)ο髥卫J较嚓P(guān)概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04JavaScript 大數(shù)據(jù)相加的問(wèn)題
寫(xiě)一個(gè)函數(shù)處理大數(shù)據(jù)的相加問(wèn)題,所謂的大數(shù)據(jù)是指超出了整型,長(zhǎng)整型之類(lèi)的常規(guī)數(shù)據(jù)類(lèi)型表示范圍的數(shù)據(jù)。實(shí)現(xiàn)語(yǔ)言不限。2011-08-08webpack3里使用uglifyjs壓縮js時(shí)打包報(bào)錯(cuò)的解決
這篇文章主要介紹了webpack3里使用uglifyjs壓縮js時(shí)打包報(bào)錯(cuò)的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12el-upload實(shí)現(xiàn)上傳文件并展示進(jìn)度條功能
這篇文章主要介紹了el-upload實(shí)現(xiàn)上傳文件并展示進(jìn)度條,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05