4

今天来实现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操作符的工作原理是什么?


耳东
766 声望51 粉丝

知乎专栏:[链接]