【build your own xxx】實現你自己的bind函式

亞古發表於2019-02-22
【build your own xxx】實現你自己的bind函式

今天來實現JavaScript的bind函式。
首先看MDN的bind函式描述:

【build your own xxx】實現你自己的bind函式
【build your own xxx】實現你自己的bind函式

從上面可以看出來,var A = B.bind(this)函式其實幹了這幾件事情:

  1. 返回一個函式,且這個函式後面執行時的this就是bind(this)傳入的this
  2. 接收引數,這些引數(如果有的話)作為bind()的第二個引數跟在this(或其他物件)後面,之後它們會被插入到目標函式的引數列表的開始位置,傳遞給繫結函式的引數會跟在它們的後面
  3. 使用new操作bind函式返回的函式時,之前傳入的this會被忽略,也就是說new的優先順序高於bind

第一步

首先實現第一步:

    Function.prototype.Zbind = function (othis) {
        var originFunc = this;
		return function () {
			originFunc.apply(othis);
		}
	}


	var obj = {

	}

	function createAgumon() {
		this.name = "agumon";
    }
	var createAgumonBind = createAgumon.Zbind(obj);
	createAgumonBind();   
    obj;// {name: "agumon"}
複製程式碼

第二步

第二步考慮傳參的問題,首先看看原生的bind函式是如何傳參的:

    var obj = {

    }
    function createAgumon(gender, age) {
        this.name = "agumon";
		this.gender = gender;
		this.age = age;
    }
    var createAgumonBind = createAgumon.bind(obj, `female`);
	createAgumonBind(22);
複製程式碼

可以看出來在bind函式中能先傳部分引數,執行bind返回的函式時可以再傳入部分引數。
自己實現:

    Function.prototype.Zbind = function (othis) {
        var originFunc = this;
		var partArgs = [].slice.call(arguments, 1);
    	var func = function() {};
		var boundFunc = function () {
			var finalArgs = partArgs.concat([].slice.call(arguments));
			return originFunc.apply(othis, finalArgs);
        }

        return boundFunc;
    }


    var obj = {

    }

    function createAgumon(gender, age) {
		this.name = "agumon";
		this.gender = gender;
		this.age = age;
    }

	var createAgumonBind = createAgumon.Zbind(obj, `female`);
	createAgumonBind(22);
    obj;// {name: "agumon", gender: "female", age: 22}
複製程式碼

第三步

使用new來呼叫bind返回的函式時,會忽略bind傳入的this
new操作和普通的函式呼叫有哪些區別?
粗略的來講,例如new F()這樣的呼叫,有以下幾個步驟:

  1. 新建一個物件,var o = new Object()
  2. 設定原型鏈,o.proto = F.prototype
  3. 把F函式體內的this繫結為o,並且執行F函式的程式碼
  4. 判斷F的返回型別:
    如果是值型別,則返回o
    如果是引用型別,則返回該引用型別物件

開始實現:

    Function.prototype.Zbind = function (othis) {
        var originFunc = this;
		var partArgs = [].slice.call(arguments, 1);
		var func = function() {};
		var boundFunc = function () {
			var finalArgs = partArgs.concat([].slice.call(arguments));
            return originFunc.apply(this instanceof boundFunc ? this : othis, finalArgs);
        }


    	return boundFunc;
    }


    var obj = {}

    function createAgumon(gender, age) {
			this.name = "agumon";
			this.gender = gender;
			this.age = age;
	}

    var createAgumonBind = createAgumon.Zbind(obj, `female`);
    var newObj = new createAgumonBind(22);
    obj // {}
    newObj // {name: "agumon", gender: "female", age: 22}
複製程式碼

關鍵的地方在於這裡:this instanceof boundFunc ? this : othis,如果是new操作的話,此時this的__proto__已經指向了boundFunc,所以使用instanceof可以檢測出是否在使用new操作

小細節

原型丟失
剛剛實現的Zbind方法有個小問題:

    Function.prototype.Zbind = function (othis) {
				var originFunc = this;
				var partArgs = [].slice.call(arguments, 1);
				var func = function() {};
				var boundFunc = function () {
					var finalArgs = partArgs.concat([].slice.call(arguments));
					return originFunc.apply(this instanceof boundFunc ? this : othis, finalArgs);
				}


				return boundFunc;
			}


			var obj = {

			}

			function createAgumon(gender, age) {
				this.name = "agumon";
				this.gender = gender;
				this.age = age;
			}
			createAgumon.prototype.college = `THU`
			var createAgumonBind = createAgumon.Zbind(obj, `female`);
			var newObj = new createAgumonBind(22);
			console.log(newObj.college)// undefined
複製程式碼

可以看出來原型鏈丟失了,newObj.college得是`THU`才行

修改:

    Function.prototype.Zbind = function (othis) {
				var originFunc = this;
				var partArgs = [].slice.call(arguments, 1);
				var func = function() {};
				var boundFunc = function () {
					var finalArgs = partArgs.concat([].slice.call(arguments));
					return originFunc.apply(this instanceof boundFunc ? this : othis, finalArgs);
				}

				func.prototype = originFunc.prototype;
				boundFunc.prototype = new func();

				return boundFunc;
			}


			var obj = {

			}

			function createAgumon(gender, age) {
				this.name = "agumon";
				this.gender = gender;
				this.age = age;
			}
			createAgumon.prototype.college = `THU`
			var createAgumonBind = createAgumon.Zbind(obj, `female`);
			var newObj = new createAgumonBind(22);
			console.log(newObj.college)// `THU`
複製程式碼

為什麼要使用func.prototype = originFunc.prototype;boundFunc.prototype = new func();,而不是直接用**boundFunc.prototype = originFunc.prototype;**是因為這樣寫的話,修改boundFunc.prototype會影響到原函式的prototype。

that`all

參考資料:
mdn-bind
javascript中,new操作符的工作原理是什麼?

相關文章