JavaScript中call、apply、bind實(shí)現(xiàn)原理詳解
前言
眾所周知 call、apply、bind 的作用都是‘改變'作用域,但是網(wǎng)上對(duì)這這‘改變'說(shuō)得含糊其辭,并未做詳細(xì)說(shuō)明,‘改變'是直接替換作用域?誰(shuí)替換誰(shuí)?怎么產(chǎn)生效果?這些問(wèn)題如果不理解清楚,就算看過(guò)手寫(xiě)實(shí)現(xiàn),估計(jì)也記不長(zhǎng)久
所以本文介紹了call、apply、bind的用法和他們各自的實(shí)現(xiàn)原理。
call
call() 方法使用一個(gè)指定的 this 值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來(lái)調(diào)用一個(gè)函數(shù)。
即:可以改變當(dāng)前函數(shù)的this指向;還會(huì)讓當(dāng)前函數(shù)執(zhí)行。
用法
function fun() { console.log(this.name, arguments) } let obj = { name: 'clying' } fun.call(obj, 'deng', 'deng') // clying [Arguments] { '0': 'deng', '1': 'deng' }
實(shí)現(xiàn)
call和apply的實(shí)現(xiàn),都是使用將函數(shù)放到字面量obj的某個(gè)屬性中,使函數(shù)中的this指向obj這個(gè)字面量對(duì)象。
簡(jiǎn)單的實(shí)現(xiàn)版本:
Function.prototype.mycall = function (context) { context = (context == null || context == undefined) ? window : new Object(context) context.fn = this context.fn() delete context.fn }
給函數(shù)原型添加mycall方法,創(chuàng)建一個(gè)上下文對(duì)象context,如果傳入的對(duì)象不存在時(shí),將指向全局window。通過(guò)給context添加fn屬性,context的fn引用調(diào)用該方法的函數(shù)fun,并執(zhí)行fun。執(zhí)行完成之后刪除該屬性fn。
當(dāng)中需要先獲取傳入的參數(shù),那它變成字符串?dāng)?shù)組。
執(zhí)行方法使用的是eval函數(shù),再通過(guò)eval計(jì)算字符串,并執(zhí)行其中代碼,返回計(jì)算結(jié)果。
升級(jí)版:
給call中傳入?yún)?shù)。
Function.prototype.mycall = function (context) { context = (context == null || context == undefined) ? window : new Object(context) context.fn = this let arr = [] for (let i = 1; i < arguments.length; i++) { arr.push('argument[' + i + ']') // ["arguments[1]", "arguments[2]"] } let r = eval('context.fn(' + arr + ')') // 執(zhí)行函數(shù)fun,并傳入?yún)?shù) delete context.fn return r }
此外,也可以通過(guò)解構(gòu)的語(yǔ)法來(lái)實(shí)現(xiàn)call。
Function.prototype.mycall = function (context, ...args) { context = (context == null || context == undefined) ? window : new Object(context) context.fn = this context.fn(...args) delete context.fn }
如果想要能夠多次調(diào)用call方法,可以將context.fn(...args)保存到變量中,最后返回即可。
Function.prototype.mycall = function (context, ...args) { context = (context == null || context == undefined) ? window : new Object(context) context.fn = this let r = context.fn(...args) delete context.fn return r }
apply
與call方法類似,call方法接收的是一個(gè)參數(shù)列表,而apply方法接收的是一個(gè)包含多個(gè)參數(shù)的數(shù)組。
用法
將函數(shù)中的this指向傳入的第一個(gè)參數(shù),第二個(gè)參數(shù)為數(shù)組。
function fun() { console.log(this.name, arguments); } let obj = { name: 'clying' } fun.apply(obj, [22, 1]) // clying Arguments(2) [22, 1]
實(shí)現(xiàn)
自己實(shí)現(xiàn)一個(gè)apply方法myapply。實(shí)現(xiàn)方法與call類似,不過(guò)在接收參數(shù)時(shí),可以使用一個(gè)args作為傳入的第二個(gè)參數(shù)。直接判斷如果未傳入第二個(gè)參數(shù),直接執(zhí)行函數(shù);否則使用eval執(zhí)行函數(shù)。
Function.prototype.myapply = function (context, args) { context = (context == null || context == undefined) ? window : new Object(context) context.fn = this if(!args) return context.fn() let r = eval('context.fn('+args+')') delete context.fn return r }
bind
bind() 方法創(chuàng)建一個(gè)新的函數(shù),不自動(dòng)執(zhí)行,需要手動(dòng)調(diào)用bind() 。這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。
用法
將obj綁定到fun函數(shù)的this上,函數(shù)fun可以使用obj內(nèi)部的屬性,和傳入的變量。
function fun() { console.log(this.name, arguments); } let obj = { name: 'clying' } let b = fun.bind(obj,2) b(3) // clying Arguments(2) [2, 3]
此外,bind方法綁定的函數(shù)還可以new一個(gè)實(shí)例,不過(guò)此時(shí)的this會(huì)發(fā)生改變。
升級(jí)版-使用原型屬性用法:
function fun() { console.log(this.name, arguments); } let obj = { name: 'clying' } fun.prototype.age = 23 let b = fun.bind(obj, 3) let instance = new b(4) console.log(instance.age); //undefined Arguments(2) [3, 4] // 23
實(shí)現(xiàn)
基本版:
bind的實(shí)現(xiàn)可以基于call和apply的基礎(chǔ)上實(shí)現(xiàn)。
因?yàn)閎ind不是立即執(zhí)行的,所以可以通過(guò)返回一個(gè)函數(shù),讓用戶手動(dòng)執(zhí)行。在返回函數(shù)中利用call或者apply傳入指定的this對(duì)象和參數(shù)。
apply實(shí)現(xiàn)bind
Function.prototype.mybind = function (context) { let that = this let bindargs = Array.prototype.slice.call(arguments, 1) return function () { let args = Array.prototype.slice.call(arguments) return that.apply(context, bindargs.concat(args)) } }
利用apply方法,主要是在獲取處理bind傳入的參數(shù),以及用戶執(zhí)行函數(shù)傳入的參數(shù)。利用Array原型方法的slice方法,截取所需的參數(shù)。
在獲取bind傳入的參數(shù)時(shí),需要從第二個(gè)參數(shù)開(kāi)始截取,所以開(kāi)始位置為1。
call實(shí)現(xiàn)bind
Function.prototype.mybind = function (context, ...args1) { let that = this return function (...args2) { return that.call(context, ...args1, ...args2) } }
call實(shí)現(xiàn)直接將參數(shù)拼接call方法的后面即可。
升級(jí)版:
bind除了可以改變this指向、用戶可以在bind后面?zhèn)魅雲(yún)?shù)也可以在用戶執(zhí)行時(shí)傳入?yún)?shù)外。還可以讓執(zhí)行函數(shù)進(jìn)行new操作。
當(dāng)一個(gè)綁定函數(shù)是用來(lái)構(gòu)建一個(gè)值的,原來(lái)提供的 this 就會(huì)被忽略。不過(guò)提供的參數(shù)列表仍然會(huì)插入到構(gòu)造函數(shù)調(diào)用時(shí)的參數(shù)列表之前。
apply
Function.prototype.mybind = function (context) { let that = this let bindargs = Array.prototype.slice.call(arguments, 1) function fBind() { let args = Array.prototype.slice.call(arguments) // 如果使用的是new,那么this會(huì)指向fBind實(shí)例,this作為當(dāng)前實(shí)例傳入 不是的話,使用context上下文對(duì)象 return that.apply(this instanceof fBind ? this : context, bindargs.concat(args)) } return fBind }
在使用new操作符時(shí),注意的是需要改變this的指向問(wèn)題,如果是new,那么this指向的是實(shí)例,不使用new則指向bind當(dāng)前傳入的第一個(gè)參數(shù)。
此外,還牽扯到原函數(shù)可以添加自身方法屬性。如果想要能夠使用fun自身的原型方法還需要使用fBind.prototype = this.prototype,實(shí)現(xiàn)原型共用。但是對(duì)于引用類型屬性值共享,不能在不改變其他實(shí)例情況下改變(一個(gè)原型方法或?qū)傩愿淖?,所有引用的都?huì)發(fā)生改變)。
Function.prototype.mybind = function (context) { let that = this let args = Array.prototype.slice.call(arguments, 1) function fBind() { // 執(zhí)行bind函數(shù) let bindargs = Array.prototype.slice.call(arguments) return that.apply(this instanceof fBind ? this : context, args.concat(bindargs)) } function Fn(){} // 兩個(gè)類的原型并未公用,而是通過(guò)原型鏈的方式找到該原型方法 Fn.prototype = this.prototype fBind.prototype = new Fn() return fBind }
對(duì)于上述情況,可以使用一個(gè)函數(shù)中間件的形式,利用原型鏈去找到原函數(shù)原型方法或?qū)傩浴?/p>
call
call與apply的差別只是處理參數(shù)的不同,其他均類似。
Function.prototype.mybind = function (context, ...args1) { let that = this function fBind(...args2) { return that.call(this instanceof fBind ? this : context, ...args1, ...args2) } function Fn() { } Fn.prototype = this.prototype fBind.prototype = new Fn() return fBind }
總結(jié)
到此這篇關(guān)于JavaScript中call、apply、bind實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)call、apply、bind原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS動(dòng)態(tài)創(chuàng)建DOM元素的方法
這篇文章主要介紹了JS動(dòng)態(tài)創(chuàng)建DOM元素的方法,涉及javascript動(dòng)態(tài)創(chuàng)建DOM元素及DOM元素事件綁定與刪除的相關(guān)技巧,需要的朋友可以參考下2015-06-06快速解決select2在bootstrap模態(tài)框中下拉框隱藏的問(wèn)題
今天小編就為大家分享一篇快速解決select2在bootstrap模態(tài)框中下拉框隱藏的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08淺析如何在Bash中調(diào)用Node運(yùn)行JS文件進(jìn)行數(shù)據(jù)通信
這篇文章主要來(lái)和大家探討在 Bash 中調(diào)用 Node 運(yùn)行 JS 文件時(shí)如何進(jìn)行數(shù)據(jù)通信,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03JS實(shí)現(xiàn)簡(jiǎn)單tab選項(xiàng)卡切換
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)簡(jiǎn)單tab選項(xiàng)卡切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10js前端如何寫(xiě)一個(gè)精確的倒計(jì)時(shí)代碼
關(guān)于寫(xiě)倒計(jì)時(shí)大家可能都都比較熟悉,使用 setTimeout 或 setInterval 就可以搞定。幾秒鐘或者幾分鐘的倒計(jì)時(shí)這樣寫(xiě)沒(méi)有問(wèn)題,但是如果是長(zhǎng)時(shí)間的倒計(jì)時(shí),這樣寫(xiě)就會(huì)不準(zhǔn)確2019-10-10利用JavaScript編寫(xiě)一個(gè)簡(jiǎn)單的1024小游戲
在每年的10月24日,我們都會(huì)慶祝程序員節(jié),這是一個(gè)向所有辛勤工作、創(chuàng)造出無(wú)數(shù)令人驚嘆應(yīng)用和系統(tǒng)的程序員們致敬的日子,為了紀(jì)念這個(gè)特殊的日子,我們將通過(guò)編寫(xiě)一個(gè)簡(jiǎn)單的1024小游戲來(lái)向所有程序員們表示敬意,本文將詳細(xì)解釋如何使用JavaScript編寫(xiě)這個(gè)小游戲2023-10-10使用Fuse.js實(shí)現(xiàn)高效的模糊搜索功能
在現(xiàn)代?Web?應(yīng)用程序中,實(shí)現(xiàn)高效的搜索功能是至關(guān)重要的,Fuse.js?是一個(gè)強(qiáng)大的?JavaScript?庫(kù),它提供了靈活的模糊搜索和文本匹配功能,使您能夠輕松實(shí)現(xiàn)出色的搜索體驗(yàn),文中代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-01-01javascript跳轉(zhuǎn)與返回和刷新頁(yè)面的實(shí)例代碼
這篇文章主要介紹了javascript跳轉(zhuǎn)與返回和刷新頁(yè)面的實(shí)例代碼,簡(jiǎn)單介紹了javascript中window.open()與window.location.href的區(qū)別,感興趣的朋友一起看看吧2019-11-11