- 爲何面試官總愛讓實現一個bind函數?
- 他想從bind中知道些什麼?
- 一個小小的bind裏面內有玄機?
今天來刨析一下實現一個bind要懂多少相關知識點,也方便咱們將零碎的知識點串聯起來。
👍 看完有用的同窗記得點個贊再走,您的鼓勵-我莫大的動力
看完能學到什麼javascript
- 實現bind
- new原理
本文章的敘事步驟java
- bind函數做用
- 模擬bind的要點
- 實現思路
- new函數特殊狀況(this&父原型)
-------------人工分割線-------------面試
返回一個可以改變this指向的函數。app
建立一個待返回的函數,函數內部利用call/apply改變指向,call/apply的參數從arguments中獲取。函數
實現代碼以下:測試
Function.prototype.myBind = function () { let exeFunc = this; let beThis = arguments[0]; let args = [].slice.call(arguments ,1); return function () { exeFunc.apply(beThis,args); } }
來份數據測試一下:this
let other = { name: 'other' } let obj = { name: 'obj', getName : function (age,height) { console.log(this.name); console.log('年齡' + age); console.log('身高' + height); } } obj.getName.myBind(other, 14, 200)();
測試結果正常。打印的是other.net
還挺簡單的是吧!但考點一般不止如此。接着看:prototype
function Person() { this.name = 'person'; this.getName = function (age, height) { console.log(this.name); console.log('age:' + age, 'height:' + height); } }
這個時候:rest
let PersonMyBind = Person.myBind(window); let per3 = new PersonMyBind(); per3.getName();
思考一下會打印person嗎?
答案:實際上per3是一個空對象。
那麼爲何會出現這樣的錯誤。這就牽扯到關於new的知識:
若是不太明白的可便宜看下這篇文章
這是一段關於new的模擬代碼
function New (constructFunc) { // 生命中間對象,最後做爲返回的實例,至關於let obj = New(Obj); => obj = res var res = {}; if(constructFunc.prototype !== null) { // 將實例的原型指向構造函數的原型 res.__proto__ = constructFunc.prototype; } // 重點重點 ret爲該構造函數執行的結果,將構造函數的this改成執行res var ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1)); // 若是構造函數有返回值,則直接返回 if((typeof rest === "object" || typeof ret === "function") && ret !== null) { return ret; } // 不然返回該實例 return res; }
其中,下面一行代碼就是致使咱們寫的bind不能如願以償將name、getName屬性建立到對象的致命緣由,且聽我細細道來:
var ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1));
當咱們執行Person.myBind()的時候,個人獲得的返回結果是一個函數:function () {exeFunc.apply(beThis,args);},來個圖明顯一點。
那麼當這一行代碼執行時:
var ret = constructFunc.apply(res, Array.prototype.slice.call(arguments, 1));
來張圖來看清new Person與new PersonMyBind()的區別:
在知道產生這種現象的緣由以後咱們該如何解決?其實很是簡單,若是是new的狀況:
let resultFunc = function () { exeFn.apply(this, args) // 這裏傳入的是this對象,對應着new過程當中的res }
因此這個時候問題就是該如何區分new Person()和Person()!答案仍是在new的實現原理中找答案,咱們能夠找到上面new的模擬代碼中的這一行:
// 將實例的原型指向構造函數的原型 res.__proto__ = constructFunc.prototype;
也就是說在執行
let resultFunc = function () { // 此時的this__proto__等於Person.prototype exeFn.apply(this, args) }
此時的this.__proto__等於Person.prototype,利用這一特性就ok了。
升級咱們的myBind
Function.prototype.myBind = function () { if(typeof this !== 'function') { throw new Error('調用者必須爲function類型'); } let exeFn = this; // this 爲待執行函數 let currentThis = arguments[0]; // 待指定的this let args = [].slice.call(arguments,1); // 剩餘的都做爲參數傳遞 let resultFunc = function () { // 區分new調用與普通調用 exeFn.apply(this.__proto__=== resultFunc.prototype ? this : currentThis, args) } return resultFunc; }
到這裏還沒結束,咱們還要解決Person加入有父原型的狀況,在知道上面的知識點後解決這個也很是easy
再升級一版:
Function.prototype.myBind = function () { if(typeof this !== 'function') { throw new Error('調用者必須爲function類型'); } let exeFn = this; // this 爲待執行函數 let currentThis = arguments[0]; // 待指定的this let args = [].slice.call(arguments,1); // 剩餘的都做爲參數傳遞 let resultFunc = function () { // 區分new調用跟普通調用 exeFn.apply(this.__proto__=== resultFunc.prototype ? this : currentThis, args) } // 維持原來函數的父原型 if (this.prototype) { resultFunc.prototype = this.prototype; } return resultFunc; }
打完收工