一文搞懂JavaScript中bind,apply,call的實(shí)現(xiàn)
bind、call和apply都是Function
原型鏈上面的方法,因此不管是使用function
聲明的函數(shù),還是箭頭函數(shù)都可以直接調(diào)用。這三個(gè)函數(shù)在使用時(shí)都可以改變this
指向,本文就帶你看看如何實(shí)現(xiàn)bind、call和apply。
bind、call和apply的用法
bind
bind()
方法可以被函數(shù)對(duì)象調(diào)用,并返回一個(gè)新創(chuàng)建的函數(shù)。
語(yǔ)法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
bind()
會(huì)將第一個(gè)參數(shù)作為新函數(shù)的this
,如果未傳入?yún)?shù)列表,或者第一個(gè)參數(shù)是null
或undefined
,那么新函數(shù)的this
將會(huì)是該函數(shù)執(zhí)行作用域的this
。使用bind()
應(yīng)注意以下事項(xiàng):
- 返回一個(gè)新的函數(shù),但是不會(huì)立即執(zhí)行該函數(shù)
- 根據(jù)傳入的參數(shù)列表綁定
this
指向,如果未傳入thisArg
,那么需要明確this的指向 - 如果是箭頭函數(shù),無(wú)法改變this,只能改變參數(shù),這一點(diǎn)我們?cè)?a href="http://www.dbjr.com.cn/article/252806.htm" target="_blank">這些情況下不建議你使用箭頭函數(shù)也講到過
舉個(gè)例子:
正常使用
function fn(a) { console.log(this, a) } const fn1 = fn.bind({x: 100}); // fn1是一個(gè)函數(shù),但是并沒有立即執(zhí)行 fn1(); // {x:100} 100 console.log(fn === fn1); // false,bind返回的是一個(gè)新的函數(shù)
箭頭函數(shù)
const fn = (a) => { console.log(this, a); } const fn1 = fn.bind({x: 100}, 100); // 返回一個(gè)新的函數(shù)fn1,不會(huì)執(zhí)行 fn1(); // window,100 箭頭函數(shù)通過bind返回的函數(shù)無(wú)法修改其this指向
未綁定this,或綁定到null、undefined
const fn = (a) => { console.log(this, a); } const fn1 = fn.bind(); // 未綁定 const fn2 = fn.bind(null); // 綁定null const fn3 = fn.bind(undefined); // 綁定undefined fn1(); // 綁定到執(zhí)行作用域,默認(rèn)為window fn2(); // 綁定到執(zhí)行作用域,默認(rèn)為window fn3(); // 綁定到執(zhí)行作用域,默認(rèn)為window
call&apply
與bind
不同,call
和apply
都是用來(lái)執(zhí)行函數(shù)的,可以解決執(zhí)行的函數(shù)的this指向問題。
語(yǔ)法:
function.call(thisArg, arg1, arg2, ...) function.apply(thisArg, argsArray)
call
的參數(shù)列表是可選的,如果傳入的thisArg
是null
或者undefined
,那么會(huì)自動(dòng)替換為全局對(duì)象;如果是傳入的原始值,則會(huì)替換為原始值對(duì)應(yīng)的包裝類型。apply
的用法和call
類似,不同點(diǎn)在于其額外傳入的參數(shù)是一個(gè)數(shù)組或類數(shù)組對(duì)象,而call
的額外參數(shù)是不確定參數(shù)。
舉個(gè)栗子:
function fn(a, b) { console.log(this, a, b); } fn.call({x: 100}, 10, 20); // {x: 100} 10 20 fn.apply({x: 100}, [10, 20]); // {x: 100} 10 20
call
和apply
無(wú)法修改箭頭函數(shù)的this指向:
const fn = (a, b) => { console.log(this, a, b); } fn.call({x: 100}, 10, 20); // Window 10 20 fn.apply({x: 100}, [10, 20]); // Window 10 20
簡(jiǎn)單回顧了以下bind、call、apply的使用,接下來(lái)就看看應(yīng)該如何來(lái)實(shí)現(xiàn)。
實(shí)現(xiàn)bind
根據(jù)我們剛剛使用的bind()
,在設(shè)計(jì)時(shí)需要如下考慮:
- 最終返回的是一個(gè)新的函數(shù),可通過
function
來(lái)聲明 - 需要綁定新函數(shù)的this
- 需要綁定運(yùn)行時(shí)的參數(shù),可通過apply或call來(lái)實(shí)現(xiàn)
實(shí)現(xiàn)代碼:
// 通過原型鏈注冊(cè)方法 // context:傳遞的上下文this;bindArgs表示需要綁定的額外參數(shù) Function.prototype.newBind = function (context, ...bindArgs) { const self = this; // 當(dāng)前調(diào)用bind的函數(shù)對(duì)象 // 返回的函數(shù)本身也是可以再傳入?yún)?shù)的 return function (...args) { // 拼接參數(shù) const newArgs = bindArgs.concat(args); return self.apply(context, newArgs) } } function fn(a,b) { console.log(this, a, b); } const fn1 = fn.newBind({x: 100}, 10); fn1(20); // {x: 100} 10 20
bind()
返回的是一個(gè)新函數(shù),執(zhí)行新函數(shù)就相當(dāng)于是通過call
或apply
來(lái)調(diào)用原函數(shù),并傳入this和參數(shù)。
實(shí)現(xiàn)call和apply
在實(shí)現(xiàn)bind
的過程中,我們使用了apply
來(lái)完成this的綁定,那么要實(shí)現(xiàn)apply
又應(yīng)該用什么來(lái)綁定this呢?可能會(huì)有小機(jī)靈鬼發(fā)現(xiàn),好像在apply
中使用call
,在call
中使用apply
也可以完成this綁定。這不就形成了嵌套嘛,不是我們最終想要的。
我們先來(lái)
call和apply的應(yīng)用:
- bind返回一個(gè)新的函數(shù),并不會(huì)執(zhí)行;call和apply會(huì)立即執(zhí)行函數(shù)
- 綁定this
- 傳入執(zhí)行參數(shù)
舉個(gè)栗子:
function fn(a, b) { console.log(this, a, b); } fn.call({x: 100}, 10, 20); // {x: 100} 10 20 fn.apply({x: 100}, [10, 20]); // {x: 100} 10 20
call和apply的實(shí)現(xiàn)效果是一樣的,都是立即執(zhí)行函數(shù),不同的是call需要傳入單個(gè)或者多個(gè)參數(shù),apply可以傳入一個(gè)參數(shù)數(shù)組。
如何在函數(shù)執(zhí)行時(shí)綁定this:
- const obj = {x: 100, fn() {this.x}}
- 執(zhí)行obj.fn(),此時(shí)fn()內(nèi)部的this指向的就是obj
- 可以借此實(shí)現(xiàn)函數(shù)綁定this
使用過Vue的朋友都知道,Vue實(shí)例其實(shí)就是一個(gè)對(duì)象,其里面的方法在調(diào)用時(shí),this就會(huì)指向當(dāng)前對(duì)象。舉個(gè)栗子:
let obj = { key: 'key', getKey: () => { return this.key; }, getKey2() { return this.key; } }; obj.getKey(); // this指向window,返回值取決于window中是否有對(duì)應(yīng)的屬性 obj.getKey2(); // this指向obj,返回 'key'
這個(gè)例子在這些情況下不建議你使用箭頭函數(shù)也是有提及的,感興趣的朋友可以去看看。根據(jù)此原理,我們就可以來(lái)嘗試給函數(shù)綁定this了:某函數(shù)調(diào)用apply
,那么我們就將這個(gè)函數(shù)添加到傳入的this對(duì)象中(如果未傳入則this為全局對(duì)象,如果傳入的是原始值,則使用其包裝類型),然后使用()
來(lái)執(zhí)行函數(shù),這個(gè)時(shí)候函數(shù)的this指向的就是我們傳入的this了。
實(shí)現(xiàn)代碼:
Function.prototype.newCall = function(context, ...args) { if (context == null) context = globalThis; // 如果傳入的上下文是null或者undefined,則使用全局globalThis,一般指向的就是window if (typeof context !== 'object') context = new Object(context); // 如果是原始類型(數(shù)字、字符串、布爾值等),則使用其包裝類型 const fnKey = Symbol(); // 使用Symbol可確保key值不會(huì)重復(fù),避免屬性覆蓋 context[fnKey] = this; // this指向的是當(dāng)前調(diào)用newCall的函數(shù) console.log(context[fnKey]); // 打印當(dāng)前函數(shù)以及上下文this console.log(context); const res = context[fnKey](...args); // 執(zhí)行函數(shù),函數(shù)的this指向?yàn)閏ontext delete context[fnKey]; // 刪除fn,防止污染 return res; // 返回結(jié)果 } fn.newCall({x: 100}, 10, 20); // {x: 100} 10 20 function fn(a,b) { console.log(this, a, b); }
這樣我們就實(shí)現(xiàn)了call
,那么apply
實(shí)現(xiàn)類似,只不過傳入的額外參數(shù)要變成數(shù)組或類數(shù)組的方式
Function.prototype.newCall = function(context, args) { if (context == null) context = globalThis; // 如果傳入的上下文是null或者undefined,則使用全局globalThis,一般指向的就是window if (typeof context !== 'object') context = new Object(context); // 如果是原始類型(數(shù)字、字符串、布爾值等),則使用其包裝類型 const fnKey = Symbol(); // 使用Symbol可確保key值不會(huì)重復(fù),避免屬性覆蓋 context[fnKey] = this; // this指向的是當(dāng)前調(diào)用newCall的函數(shù) console.log(context[fnKey]); // 打印當(dāng)前函數(shù)以及上下文this console.log(context); const res = context[fnKey](...args); // 執(zhí)行函數(shù),函數(shù)的this指向?yàn)閏ontext delete context[fnKey]; // 刪除fn,防止污染 return res; // 返回結(jié)果 } fn.newCall({x: 100}, 10, 20); // {x: 100} 10 20 function fn(a,b) { console.log(this, a, b); }
注意打印的當(dāng)前函數(shù)以及上下文:
實(shí)現(xiàn)call
和apply
與bind
有很大的不同就是如何來(lái)處理this
綁定。
總結(jié)
學(xué)會(huì)了如何實(shí)現(xiàn)bind、call和apply,對(duì)于理解如何使用,以及如何避免潛在的錯(cuò)誤有很大的幫助。特別是call
和apply
,我們?cè)趯?shí)現(xiàn)的時(shí)候借助于對(duì)象內(nèi)部的非箭頭函數(shù),其this指向?qū)ο笞陨?/strong>這一基礎(chǔ)知識(shí),實(shí)現(xiàn)了this綁定。如果還未搞清楚的朋友,可以將代碼運(yùn)行起來(lái)看看,也許能幫助你更好的理解。
以上就是一文搞懂JavaScript中bind,apply,call的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript bind apply call的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入理解JavaScript系列(26):設(shè)計(jì)模式之構(gòu)造函數(shù)模式詳解
這篇文章主要介紹了深入理解JavaScript系列(26):設(shè)計(jì)模式之構(gòu)造函數(shù)模式詳解,本文講解了基本用法、構(gòu)造函數(shù)與原型、只能用new嗎?、強(qiáng)制使用new、原始包裝函數(shù)等內(nèi)容,需要的朋友可以參考下2015-03-03如何利用微信小程序獲取OneNet平臺(tái)數(shù)據(jù)顯示溫濕度
最近在工作中遇到了一個(gè)需求,需要顯示溫濕度,網(wǎng)上找了一圈沒找到解決方法,所以只能自己寫一個(gè),這篇文章主要給大家介紹了關(guān)于如何利用微信小程序獲取OneNet平臺(tái)數(shù)據(jù)顯示溫濕度的相關(guān)資料,需要的朋友可以參考下2022-03-03跟我學(xué)習(xí)javascript的for循環(huán)和for...in循環(huán)
跟我學(xué)習(xí)javascript的for循環(huán)和for...in循環(huán),它們是JavaScript中提供了兩種方式迭代對(duì)象,本文就和大家一起學(xué)習(xí)for循環(huán)和for...in循環(huán),感興趣的小伙伴們可以參考一下2015-11-11Javascript中類式繼承和原型式繼承的實(shí)現(xiàn)方法和區(qū)別之處
其它的面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言都是通過關(guān)鍵字來(lái)解決繼承的問題。但是javascript中并沒有定義這種實(shí)現(xiàn)的機(jī)制。接下來(lái)通過本文給大家介紹Javascript中類式繼承和原型式繼承的實(shí)現(xiàn)方法和區(qū)別,需要的朋友可以參考下2017-04-04ES6 proxy和reflect的使用方法與應(yīng)用實(shí)例分析
這篇文章主要介紹了ES6 proxy和reflect的使用方法,結(jié)合具體實(shí)例形式分析了ES6 proxy和reflect基本功能、原理、使用方法與操作注意事項(xiàng),需要的朋友可以參考下2020-02-02javascript中的nextSibling使用陷(da)阱(keng)
關(guān)于HTML/XML節(jié)點(diǎn)的問題,在IE中nextSibling不會(huì)返回文本節(jié)點(diǎn),而chrome或者firefox等會(huì)返回文本節(jié)點(diǎn)2014-05-05JS實(shí)現(xiàn)類似51job上的地區(qū)選擇效果示例
這篇文章主要介紹了JS實(shí)現(xiàn)類似51job上的地區(qū)選擇效果,結(jié)合完整實(shí)例形式分析了javascript基于鼠標(biāo)事件響應(yīng)實(shí)現(xiàn)頁(yè)面元素動(dòng)態(tài)變換的相關(guān)操作技巧,需要的朋友可以參考下2016-11-11