Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

聊聊 underscore 里面是怎样实现 bind() 的? #36

Open
jyzwf opened this issue Jan 6, 2018 · 0 comments
Open

聊聊 underscore 里面是怎样实现 bind() 的? #36

jyzwf opened this issue Jan 6, 2018 · 0 comments

Comments

@jyzwf
Copy link
Owner

jyzwf commented Jan 6, 2018

现在的函数,原型上就有 bind 方法,但是如果我们在不支持该方法的浏览器上,要怎么来实现呢?
image

简单的bind无非就是使用 call或者apply,然后返回一个函数

要点:是绑定后的函数与被绑定的函数在同一条原型链上

// 1
Function.prototype.bind = function (context) {
    let self = this;
    return function () {
        return self.apply(context, arguments)
    }
}

// 2 
Function.prototype.bind = function (cxt) {
    var args = Array.prototype.slice.call(arguments, 1),
        self = this;
    return function () {
        let innerArgs = Array.prototype.slice.call(arguments)
        let finalArgs = args.concat(innerArgs)
        return self.apply(cxt, finalArgs)
    }
}



// 3
Function.prototype.bind = function (cxt) {
    let args = Array.prototype.slice(arguments, 1),
        F = function () { },
        self = this,
        bound = function () {
            let innerArgs = Array.prototype.slice.call(arguments),
                finalArgs = args.concat(innerArgs)
            return self.apply((this instanceof F ? this : cxt), finalArgs)
        }
    // // 维护原型关系
    F.prototype = this.prototype;
    bound.prototype = new F();
    return bound
}

// 4
Function.prototype.bind = Function.prototype.bind || function (oThis) { // 偏函数
    if (typeof this !== "function") {
        // closest thing possible to the ECMAScript 5
        // internal IsCallable function
        throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP = function () { }, //用于原型继承,防止直接引用被修改原型
        fBound = function () {
            /**
             * this instanceof fNOP 为了防止以下的情况,例如 Bloomer 是调用 bind 函数返回的函数的实例
             * 和setTimeout一起使用
             * 一般情况下setTimeout()的this指向window或global对象。
             * 当使用类的方法时需要this指向类实例,就可以使用bind()将this绑定到回调函数来管理实例。
             * function Bloomer() {  
             *      this.petalCount = Math.ceil(Math.random() * 12) + 1;
             * }
             * 
             * Bloomer.prototype.bloom = function() {  
             *      window.setTimeout(this.declare.bind(this), 1000);
             * }
             * 
             * Bloomer.prototype.declare = function() {  
             *       console.log('我有 ' + this.petalCount + ' 朵花瓣!');
             * }
             */
            return fToBind.apply(this instanceof fNOP    // 这里的判断为 true 的情况见下面,这时候的 this 就是 fBound 函数
                ? this
                : oThis || this,    // 为false 的情况就是上面所讲的 setTimeout 情况,这时调整 this 指向,没有指定的就直接为 this
                aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
}




function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype.toString = function () {
    return this.x + ',' + this.y;
};

var p = new Point(1, 2);
p.toString(); // '1,2'


var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0 /*x*/ );
YAxisPoint.prototype.aaa = 66666666
console.log(YAxisPoint.prototype)
console.log(Point.prototype)

// this instanceof fNOP 
var YAxisPoint = Point.bind(null, 0/*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true  
axisPoint instanceof YAxisPoint; // true  

p80106-151719

那么underscore 里面是怎么实现的呢?

// 决定一个函数是作为构造函数还是接受参数的普通函数
    var executeBound = function (sourceFunc, boundFunc, context, callingContext, args) {
        // 这里的和上文的 this instanceof fNOP == false 一样
        if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args)

        // 下面的几步骤,基本和 new 一个构造函数的过程一样,详见如下:
        // https://github.com/jyzwf/blog/issues/27
        // 获取继承sourceFun原型的一个对象
        var self = baseCreate(sourceFunc.prototype)     // 类似上文的 fNOP 
        // 执行构造函数,并将里面的 this 指向 self ,返回结果
        var result = sourceFunc.apply(self, args)
        // 如果 返回的结果是一个对象,就直接返回该对象
        if (_.isObject(result)) return result
        // 否则返回之前 new 出来的对象
        return self

    }


    _.bind = restArgs(function (func, context, args) {
        /** 
         * startIndex = 2
         * 返回如下函数 
         * function () {
            var length = Math.max(arguments.length - startIndex, 0), // 防止startIndex 过大
                rest = Array(length),
                index = 0;

            for (; index < length; index++) { // 将从startIndex开始的后面参数放入数组
                rest[index] = arguments[index + startIndex] // 从args 开始收集参数
            }

            
            switch (startIndex) {
                case 0:
                    return func.call(this, rest);
                case 1:
                    return func.call(this, arguments[0], rest);

                    // 在_.invoke()使用
                case 2: // 调用这个 case
                    return func.call(this, arguments[0], arguments[1], rest);
            }


        }
        */
        if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function')
        var bound = restArgs(function (callArgs) {      // 收集剩余函数
            /** 
             * startIndex = 0
             * 
             * function () {
            var length = Math.max(arguments.length - startIndex, 0), // 防止startIndex 过大
                rest = Array(length),
                index = 0;

            for (; index < length; index++) { // 将从startIndex开始的后面参数放入数组
                rest[index] = arguments[index + startIndex] // 从args 开始收集参数
            }

           
            switch (startIndex) {
                case 0:  // 调用这个 case
                    return func.call(this, rest);
                case 1:
                    return func.call(this, arguments[0], rest);

                    // 在_.invoke()使用
                case 2: 
                    return func.call(this, arguments[0], arguments[1], rest);
            }

        }
             */
            return executeBound(func, bound, context, this, args.concat(callArgs))
        })

        return bound
    })

这里我们看到出现了两个 restArgs函数,这个函数在 underscore里面很重要,我们看看他长什么样 ?

// 类似es6的剩余参数集合
var restArgs = function (func, startIndex) {
        startIndex = startIndex == null ? func.length - 1 : +startIndex

        return function () {
            var length = Math.max(arguments.length - startIndex, 0), // 防止startIndex 过大
                rest = Array(length),
                index = 0;

            for (; index < length; index++) { // 将从startIndex开始的后面参数放入数组
                rest[index] = arguments[index + startIndex]
            }

            // switch 干嘛的?????????????
            switch (startIndex) {
                case 0:
                    return func.call(this, rest);
                case 1:
                    return func.call(this, arguments[0], rest);

                    // 在_.invoke()使用
                case 2:
                    return func.call(this, arguments[0], arguments[1], rest);
            }

            // 收集startIndex 之前的参数,并将 startIndex 开始的后面的参数包装为一个数组使用
            var args = Array(startIndex + 1);
            for (index = 0; index < startIndex; index++) {
                args[index] = arguments[index]
            }

            // 将args的最后一个参数设置为之前的 rest
            args[startIndex] = rest;
            return func.apply(this, args)
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant