摘要:返回的新函數(shù)調(diào)用時(shí)也可以向原函數(shù)傳遞實(shí)參,這里就涉及順序問題。返回的新函數(shù)被當(dāng)成構(gòu)造函數(shù)調(diào)用函數(shù)后返回的新函數(shù),也可以被當(dāng)做構(gòu)造函數(shù)。使用,以原函數(shù)作為新對(duì)象的原型創(chuàng)建對(duì)象測(cè)試打印打印函數(shù)源碼已實(shí)現(xiàn)完成,希望對(duì)你有幫助。
bind函數(shù)
bind 函數(shù)掛在 Function 的原型上
Function.prototype.bind
創(chuàng)建的函數(shù)都可以直接調(diào)用 bind,使用:
function func(){ console.log(this) } func.bind(); // 用函數(shù)來調(diào)用
bind 的作用:
bind() 方法調(diào)用后會(huì)創(chuàng)建一個(gè)新函數(shù)。當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí),bind() 的第一個(gè)參數(shù)將作為新函數(shù)運(yùn)行時(shí)的 this的值,之后的序列參數(shù)將會(huì)在傳遞的實(shí)參前傳入作為新函數(shù)的參數(shù)。
bind 接收的參數(shù)
func.bind(thisArg[,arg1,arg2...argN])
第一個(gè)參數(shù)thisArg,當(dāng) func 函數(shù)被調(diào)用時(shí),該參數(shù)會(huì)作為 func 函數(shù)運(yùn)行時(shí)的 this 指向。當(dāng)使用 new 操作符調(diào)用綁定函數(shù)時(shí),該參數(shù)無效。
[,arg1,arg2...argN] 作為實(shí)參傳遞給 func 函數(shù)。
bind 返回值
返回一個(gè)新函數(shù)
注意:這和函數(shù)調(diào)用 call/apply 改變this指向有所不同。調(diào)用call/apply 會(huì)把原函數(shù)直接執(zhí)行了。
舉個(gè)例子說明:
function func(){ console.log(this) } // 用call func.call({a:1}); // func函數(shù)被執(zhí)行了,打?。簕a:1} // 用bind let newFunc = func.bind({}); // 返回新函數(shù) newFunc(); // 只有當(dāng)返回的新函數(shù)執(zhí)行,func函數(shù)才會(huì)被執(zhí)行
從以上得到如下信息:
bind被函數(shù)調(diào)用
返回一個(gè)新函數(shù)
能改變函數(shù)this指向
可以傳入?yún)?shù)
深入bind 使用以上知道了 bind 函數(shù)的作用以及使用方式,接下深入到 bind 函數(shù)的使用中,具體介紹三個(gè)方面的使用,這也是之后模擬實(shí)現(xiàn) bind 函數(shù)的要點(diǎn)。
改變函數(shù)運(yùn)行時(shí)this指向
傳遞參數(shù)
返回的新函數(shù)被當(dāng)成構(gòu)造函數(shù)
改變函數(shù)運(yùn)行時(shí)this指向當(dāng)調(diào)用 bind 函數(shù)后,bind 函數(shù)的第一個(gè)參數(shù)就是原函數(shù)作用域中 this 指向的值。
function func(){ console.log(this); } let newFunc = func.bind({a:1}); newFunc(); // 打?。簕a:1} let newFunc2 = func.bind([1,2,3]); newFunc2(); // 打?。篬1,2,3] let newFunc3 = func.bind(1); newFunc3(); // 打印:Number:{1} let newFunc4 = func.bind(undefined/null); newFunc4(); // 打?。簑indow
以上要注意,當(dāng)傳入為 null 或者 undefined 時(shí),在非嚴(yán)格模式下,this 指向?yàn)?window。
當(dāng)傳入為簡(jiǎn)單值時(shí),內(nèi)部會(huì)將簡(jiǎn)單的值包裝成對(duì)應(yīng)類型的對(duì)象,數(shù)字就調(diào)用 Number 方法包裝;字符串就調(diào)用 String 方法包裝;true/false 就調(diào)用 Boolean 方法包裝。要想取到原始值,可以調(diào)用 valueOf 方法。
Number(1).valueOf(); // 1 String("hello").valueOf(); // hello Boolean(true).valueOf(); // true
當(dāng)多次調(diào)用 bind 函數(shù)時(shí),以第一次調(diào)用 bind 函數(shù)的改變 this 指向的值為準(zhǔn)。
function func(){ console.log(this); } let newFunc = func.bind({a:1}).bind(1).bind(["a","b","c"]); newFunc(); // 打?。簕a: 1}傳遞的參數(shù)
從 bind 的第二個(gè)參數(shù)開始,是向原函數(shù)傳遞的實(shí)參。bind 返回的新函數(shù)調(diào)用時(shí)也可以向原函數(shù)傳遞實(shí)參,這里就涉及順序問題。
function func(a,b,c){ console.log(a,b,c); // 打印傳入的實(shí)參 } let newFunc = func.bind({},1,2); newFunc(3)
打印結(jié)果為1,2,3。
可以看到,在 bind 中傳遞的參數(shù)要先傳入到原函數(shù)中。
調(diào)用 bind 函數(shù)后返回的新函數(shù),也可以被當(dāng)做構(gòu)造函數(shù)。通過新函數(shù)創(chuàng)建的實(shí)例,可以找到原函數(shù)的原型上。
// 原函數(shù) function func(name){ console.log(this); // 打?。和ㄟ^{name:"wy"} this.name = name; } func.prototype.hello = function(){ console.log(this.name) } let obj = {a:1} // 調(diào)用bind,返回新函數(shù) let newFunc = func.bind(obj); // 把新函數(shù)作為構(gòu)造函數(shù),創(chuàng)建實(shí)例 let o = new newFunc("seven"); console.log(o.hello()); // 打印:"seven" console.log(obj); // 打?。簕a:1}
新函數(shù)被當(dāng)成了構(gòu)造函數(shù),原函數(shù)func 中的 this 不再指向傳入給 bind 的第一個(gè)參數(shù),而是指向用 new 創(chuàng)建的實(shí)例。在通過實(shí)例 o 找原型上的方法 hello 時(shí),能夠找到原函數(shù) func 原型上的方法。
在模擬實(shí)現(xiàn) bind 特別要注意這一塊的實(shí)現(xiàn),這也是面試的重點(diǎn),會(huì)涉及到繼承。
bind函數(shù)應(yīng)用場(chǎng)景以上只是說了 bind 函數(shù)時(shí)如何使用的,學(xué)會(huì)了使用,要把它放在業(yè)務(wù)場(chǎng)景中來解決一些現(xiàn)實(shí)問題。
場(chǎng)景一先來一個(gè)布局:
需求:點(diǎn)擊每一個(gè) li 元素,延遲1000ms后,改變 li 元素的顏色,
let lis = document.querySelectorAll("#list li"); for(var i = 0; i < lis.length; i++){ lis[i].onclick = function(){ setTimeout(function(){ this.style.color = "red" },1000) } }
以上代碼點(diǎn)擊每一個(gè) li,并不會(huì)改變顏色,因?yàn)槎〞r(shí)器回調(diào)函數(shù)的 this 指向的不是點(diǎn)擊的 li,而是window,(當(dāng)然你也可以使用箭頭函數(shù),let之類來解決,這里討論的主要是用bind來解決)。此時(shí)就需要改變回調(diào)函數(shù)的 this 指向。能改變函數(shù) this 指向的有:call、apply、bind。那么選擇哪一個(gè)呢?根據(jù)場(chǎng)景來定,這里的場(chǎng)景是在1000ms之后才執(zhí)行回調(diào)函數(shù),所以不能選擇使用call、apply,因?yàn)樗鼈儠?huì)立即執(zhí)行函數(shù),所以這個(gè)場(chǎng)景應(yīng)該選擇使用 bind解決。
setTimeout(function(){ this.style.color = "red" }.bind(this),1000)場(chǎng)景二
有時(shí)會(huì)使用面向?qū)ο蟮姆绞絹斫M織代碼,涉及到把事件處理函數(shù)拆分在原型上,然后把這些掛在原型上的方法賦值給事件,此時(shí)的函數(shù)在事件觸發(fā)時(shí)this都指向了元素,進(jìn)而需要在函數(shù)中訪問實(shí)例上的屬性時(shí),便不能找到成。
function Modal(options){ this.options = options; } Modal.prototype.init = function(){ this.el.onclick = this.clickHandler; // 此方法掛載原型上 } Modal.prototype.clickHandler = function(){ console.log(this.left); // 此時(shí)點(diǎn)擊元素執(zhí)行該函數(shù),this指向元素,不能找到left } let m = new Modal({ el: document.querySelector("#list"), left: 300 }) m.init(); // 啟動(dòng)應(yīng)用
以上代碼,在 init 函數(shù)中,給元素綁定事件,事件處理函數(shù)掛在原型上,使用 this 來訪問。當(dāng)點(diǎn)擊元素時(shí),在 clickHandler 函數(shù)中需要拿到實(shí)例的 left 屬性,但此時(shí) clickHandler 函數(shù)中的 this 指向的是元素,而不是實(shí)例,所以拿不到。要改變 clickHandler 函數(shù) this 的指向,此時(shí)就需要用到 bind。
Modal.prototype.init = function(){ this.el.onclick = this.clickHandler.bind(this) }
以上場(chǎng)景只是 bind 使用的冰山一角,它本質(zhì)要做的事情是改變 this 的指向,達(dá)到預(yù)期目的。掌握了 bind 的作用以及應(yīng)用的場(chǎng)景,在腦海中就會(huì)樹立一個(gè)印象:當(dāng)需要改變this指向,并不立即執(zhí)行函數(shù)時(shí),就能想到 bind。
模擬實(shí)現(xiàn)為什么要自己去實(shí)現(xiàn)一個(gè)bind函數(shù)呢?
bind()函數(shù)在 ECMA-262 第五版才被加入;它可能無法在所有瀏覽器上運(yùn)行(ie8以下)。
面試用,讓面試官找不到拒絕你的理由
抓住 bind 使用的幾個(gè)特征,把這些點(diǎn)一一實(shí)現(xiàn)就OK,具體的點(diǎn):
被函數(shù)調(diào)用
返回新函數(shù)
傳遞參數(shù)
改變函數(shù)運(yùn)行時(shí)this指向
新函數(shù)被當(dāng)做構(gòu)造函數(shù)時(shí)處理
被函數(shù)調(diào)用,可以直接掛在Function的原型上,為了補(bǔ)缺那些不支持的瀏覽器,不用再為支持的瀏覽器添加,可以做如下判斷:
if(!Function.prototype.bind) { Function.prototype.bind = function(){ } }
這種行為也叫作 polyfill,為不支持的瀏覽器添加某項(xiàng)功能,以達(dá)到抹平瀏覽器之間的差距。
注意:如果瀏覽器支持,方便自己測(cè)試,可以把 if 條件去掉,或者把 bind 改一個(gè)名字。在下文準(zhǔn)備改名字為 bind2,方便測(cè)試。
調(diào)用 bind 后會(huì)返回一個(gè)新的函數(shù),當(dāng)新函數(shù)被調(diào)用,原函數(shù)隨之也被調(diào)用。
Function.prototype.bind2 = function(thisArg,...args){ let funcThis = this; // 函數(shù)調(diào)用bind,this指向原函數(shù) // 返回新函數(shù) return function (...rest) { return funcThis.apply(thisArg,[...args,...rest]/*bind2傳遞的實(shí)參優(yōu)先于新函數(shù)的實(shí)參*/) } } // 測(cè)試 function func(a,b,c){ console.log(this) console.log(a,b,c) } let newFunc = func.bind2({a:1},1,2); newFunc(3); // 打?。簕a: 1} // 打印:1 2 3
以上這個(gè)函數(shù)已經(jīng)能夠改變?cè)瘮?shù) this 的指向,并傳遞正確順序的參數(shù)。接下來就是比較難理解的地方,當(dāng)新函數(shù)被當(dāng)做構(gòu)造函數(shù)的情況。
需要作出兩個(gè)地方的改變:
新返回的函數(shù)要繼承原函數(shù)原型上的屬性
原函數(shù)改變this問題。如果用new調(diào)用,則原函數(shù)this指向應(yīng)該是新函數(shù)中this的值;否則為傳遞的thisArg的值。
先做繼承,讓新函數(shù)繼承原函數(shù)的原型,維持原來的原型關(guān)系。匿名函數(shù)沒辦法引用,所以給新函數(shù)起一個(gè)名字。
Function.prototype.bind2 = function(thisArg,...args){ let funcThis = this; // 函數(shù)調(diào)用bind,this指向原函數(shù) // 要返回的新函數(shù) let fBound = function (...rest) { return funcThis.apply(thisArg,[...args,...rest]/*bind2傳遞的實(shí)參優(yōu)先于新函數(shù)的實(shí)參*/) } // 不是所有函數(shù)都有prototype屬性,比如 Function.prototype就沒有。 if(funcThis.prototype){ // 使用Object.create,以原函數(shù)prototype作為新對(duì)象的原型創(chuàng)建對(duì)象 fBound.prototype = Object.create(funcThis.prototype); } return fBound; } // 測(cè)試 function func(name){ console.log(this); // {a: 1} this.name = name; } func.prototype.hello = function(){ console.log(this.name); // undefined } let newFunc = func.bind2({a:1}); let o = new newFunc("seven") o.hello(); // 打?。簕a: 1} // 打印:undefined
以上代碼,新建的實(shí)例 o 能夠調(diào)用到 hello 這個(gè)方法,說明繼承已經(jīng)實(shí)現(xiàn),能夠訪問新函數(shù)上原型方法。
接下來是關(guān)于 this 指向問題,上面例子中,使用了 new 運(yùn)算符調(diào)用函數(shù),那么原函數(shù)中,this 應(yīng)該指向?qū)嵗艑?duì)。所以需要在改變 this 指向的 apply 那里對(duì)是否是使用 new 操作符調(diào)用的做判斷。
用到的操作符是 instanceof,作用是判斷一個(gè)函數(shù)的原型是否在一個(gè)對(duì)象的原型鏈上,是的話返回true,否則返回false。測(cè)試如下:
function Person(){} let p = new Person(); console.log(p instanceof Person); // true console.log(p instanceof Object); // true console.log(p instanceof Array); // fasle
也可以用 instanceof 在構(gòu)造函數(shù)中判斷是否是通過 new 來調(diào)用的。如果是用 new 來調(diào)用,說明函數(shù)中 this 對(duì)象的原型鏈上存在函數(shù)的原型,會(huì)返回true。
function Person(){ console.log(this instanceof Person); // true } new Person();
回到我們的 bind2 函數(shù)上,當(dāng)調(diào)用 bind2 后返回了新函數(shù) fBound,當(dāng)使用 new 調(diào)用構(gòu)造函數(shù)時(shí),實(shí)際上調(diào)用的就是 fBound 這個(gè)函數(shù),所以只需要在 fBound 函數(shù)中利用 instanceof 來判斷是否是用 new 來調(diào)用即可。
Function.prototype.bind2 = function(thisArg,...args){ let funcThis = this; // 函數(shù)調(diào)用bind,this指向原函數(shù) // 要返回的新函數(shù) let fBound = function (...rest) { // 如果是new調(diào)用的,原函數(shù)this指向新函數(shù)中創(chuàng)建的實(shí)例對(duì)象 // 不是new調(diào)用,依然是調(diào)用bind2傳遞的第一個(gè)參數(shù) thisArg = this instanceof fBound ? this : thisArg; return funcThis.apply(thisArg,[...args,...rest]/*bind2傳遞的實(shí)參優(yōu)先于新函數(shù)的實(shí)參*/) } // 不是所有函數(shù)都有prototype屬性,比如 Function.prototype就沒有。 if(funcThis.prototype){ // 使用Object.create,以原函數(shù)prototype作為新對(duì)象的原型創(chuàng)建對(duì)象 fBound.prototype = Object.create(funcThis.prototype); } return fBound; } // 測(cè)試 function func(name){ console.log(this); // {a: 1} this.name = name; } func.prototype.hello = function(){ console.log(this.name); // undefined } let newFunc = func.bind2({a:1}); let o = new newFunc("seven") o.hello(); // 打?。簕name:"seven"} // 打?。?seven"
bind 函數(shù)源碼已實(shí)現(xiàn)完成,希望對(duì)你有幫助。
如有偏差歡迎指正學(xué)習(xí),謝謝。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100481.html
摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對(duì)象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器,提供的值被忽略,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)。 bind() bind() 方法會(huì)創(chuàng)建一個(gè)新函數(shù),當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí),它的 this 值是傳遞給 bind() 的第一個(gè)參數(shù),傳入bind方法的第二個(gè)以及以后的參數(shù)加上綁定函數(shù)運(yùn)行時(shí)本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)。bind返回的綁定函數(shù)也能使用 n...
摘要:閉包的學(xué)術(shù)定義先來參考下各大權(quán)威對(duì)閉包的學(xué)術(shù)定義百科閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。 前言 上一章講解了閉包的底層實(shí)現(xiàn)細(xì)節(jié),我想大家對(duì)閉包的概念應(yīng)該也有了個(gè)大概印象,但是真要用簡(jiǎn)短的幾句話來說清楚,這還真不是件容易的事。這里我們就來總結(jié)提煉下閉包的概念,以應(yīng)付那些非專人士的心血來潮。 閉包的學(xué)術(shù)...
JavaScript筆試部分 點(diǎn)擊關(guān)注本公眾號(hào)獲取文檔最新更新,并可以領(lǐng)取配套于本指南的 《前端面試手冊(cè)》 以及最標(biāo)準(zhǔn)的簡(jiǎn)歷模板. 實(shí)現(xiàn)防抖函數(shù)(debounce) 防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。 那么與節(jié)流函數(shù)的區(qū)別直接看這個(gè)動(dòng)畫實(shí)現(xiàn)即可。 showImg(https://segmentfault.com/img/remote/146000002...
摘要:之前寫過一篇文章面試官問能否模擬實(shí)現(xiàn)的和方法就是利用對(duì)象上的函數(shù)指向這個(gè)對(duì)象,來模擬實(shí)現(xiàn)和的。雖然實(shí)際使用時(shí)不會(huì)顯示返回,但面試官會(huì)問到。非嚴(yán)格模式下,和,指向全局對(duì)象 前言 面試官出很多考題,基本都會(huì)變著方式來考察this指向,看候選人對(duì)JS基礎(chǔ)知識(shí)是否扎實(shí)。讀者可以先拉到底部看總結(jié),再谷歌(或各技術(shù)平臺(tái))搜索幾篇類似文章,看筆者寫的文章和別人有什么不同(歡迎在評(píng)論區(qū)評(píng)論不同之處),...
閱讀 1432·2021-11-15 11:38
閱讀 3582·2021-11-09 09:47
閱讀 1984·2021-09-27 13:36
閱讀 3230·2021-09-22 15:17
閱讀 2565·2021-09-13 10:27
閱讀 2878·2019-08-30 15:44
閱讀 1193·2019-08-27 10:53
閱讀 2721·2019-08-26 14:00