玩轉(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)用的結(jié)果。
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
。(關(guān)于閉包的介紹可以看這篇文章->閉包的應用場景) - 在返回的函數(shù)
fn
中,首先判斷是否通過new
關(guān)鍵字調(diào)用了函數(shù)。這里需要注意一點,如果返回出去的函數(shù)被當作構(gòu)造函數(shù)使用,即使用new
關(guān)鍵字調(diào)用時,this
的值會指向新創(chuàng)建的實例對象。通過檢查this instanceof fn
,可以判斷返回出去的函數(shù)是否被作為構(gòu)造函數(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 {}
總結(jié)一下,這三個函數(shù)都是用于改變函數(shù)內(nèi) this
對象的指向,只是使用方式有不同,其中 apply 傳遞多個參數(shù)使用數(shù)組的形式,call 則直接傳遞多個參數(shù),而 bind 則可以將綁定時傳遞的參數(shù)保留到調(diào)用時直接使用,支持柯里化,同時 bind 不會直接調(diào)用,綁定之后返回一個新函數(shù),然后通過調(diào)用新函數(shù)再執(zhí)行。
到此這篇關(guān)于玩轉(zhuǎn)JavaScript函數(shù):apply/call/bind技巧的文章就介紹到這了,更多相關(guān)JavaScript apply、call、bind 函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(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實現(xiàn)(畫筆大小 顏色 橡皮的實例)
下面小編就為大家分享一篇javaScript canvas實現(xiàn)(畫筆大小 顏色 橡皮的實例),具有很好的參考價值,希望對大家有所幫助2017-11-11js前端實現(xiàn)圖片懶加載(lazyload)的兩種方式
本篇文章主要介紹了js前端實現(xiàn)圖片懶加載(lazyload)的兩種方式 ,使用圖片懶加載可以提高網(wǎng)頁運行速度,有興趣的可以了解一下。2017-04-04《javascript設計模式》學習筆記三:Javascript面向?qū)ο蟪绦蛟O計單例模式原理與實現(xiàn)方法分析
這篇文章主要介紹了Javascript面向?qū)ο蟪绦蛟O計單例模式原理與實現(xiàn)方法,結(jié)合實例形式分析了《javascript設計模式》中Javascript面向?qū)ο髥卫J较嚓P(guān)概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04webpack3里使用uglifyjs壓縮js時打包報錯的解決
這篇文章主要介紹了webpack3里使用uglifyjs壓縮js時打包報錯的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12