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