【build your own xxx】实现你自己的bind函数

347 阅读3分钟

今天来实现JavaScript的bind函数。 首先看MDN的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操作符的工作原理是什么?