優雅手撕bind函數(面試官常問)




優雅手撕bind函數


前言:
  • 為什么面試官總愛讓實現一個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;
    }

打完收工


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM