前言:
- 為什麼面試官總愛讓實現一個bind函式?
- 他想從bind中知道些什麼?
- 一個小小的bind裡面內有玄機?
今天來刨析一下實現一個bind要懂多少相關知識點,也方便我們將零碎的知識點串聯起來。
? 看完有用的同學記得點個贊再走,您的鼓勵-我莫大的動力
看完能學到什麼
- 實現bind
- new原理
本文章的敘事步驟
- bind函式作用
- 模擬bind的要點
- 實現思路
- new函式特殊情況(this&父原型)
-------------人工分割線-------------
bind函式的作用
返回一個能夠改變this指向的函式。
模擬bind的要點
- 改變this指向
- 返回函式
實現思路
建立一個待返回的函式,函式內部利用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);
}
}
來份資料測試一下:
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
還挺簡單的是吧!但考點通常不止如此。接著看:
function Person() {
this.name = 'person';
this.getName = function (age, height) {
console.log(this.name);
console.log('age:' + age, 'height:' + height);
}
}
這個時候:
let PersonMyBind = Person.myBind(window);
let per3 = new PersonMyBind();
per3.getName();
思考一下會列印person嗎?
答案:實際上per3是一個空物件。
new函式特殊情況-this
那麼為什麼會出現這樣的錯誤。這就牽扯到關於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;
}
new函式特殊情況-父原型
到這裡還沒結束,我們還要解決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;
}
打完收工