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



優雅手撕bind函數


前言:
  • 爲何面試官總愛讓實現一個bind函數?
  • 他想從bind中知道些什麼?
  • 一個小小的bind裏面內有玄機?
    今天來刨析一下實現一個bind要懂多少相關知識點,也方便咱們將零碎的知識點串聯起來。

👍 看完有用的同窗記得點個贊再走,您的鼓勵-我莫大的動力

看完能學到什麼javascript

  • 實現bind
  • new原理

本文章的敘事步驟java

  • bind函數做用
  • 模擬bind的要點
  • 實現思路
  • new函數特殊狀況(this&父原型)

-------------人工分割線-------------面試

bind函數的做用

返回一個可以改變this指向的函數。app

模擬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);
        }
    }

來份數據測試一下: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函數特殊狀況-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;
    }

打完收工

相關文章
相關標籤/搜索