bind、apply、call 的区别

  • apply:参数为 this 绑定的对象,要传入的参数数组,返回结果
  • call:参数为 this 绑定的对象,要传入的参数(可多个),返回结果
  • bind:参数为 this 绑定的对象,要传入的参数(可多个),返回原函数的拷贝

bind 的实现

简化版(new 操作有问题)

Function.prototype.myBind = function () {
    let _this = this;
    let _thisArg = arguments[0];
    let args = Array.prototype.slice.call(arguments, 1);
    return function () {
        // 连接 bind 函数参数与被 bind 函数参数
        let funcArgs = args.concat(Array.prototype.slice.call(arguments));
        // 通过 apply 修改 this 指向,同时返回结果
        return _this.apply(_thisArg, funcArgs);
    };
};

修复版

new 主要做了以下操作

  1. 创建了一个全新的对象。
  2. 这个对象会被执行[[Prototype]](也就是__proto__)链接。
  3. this 会绑定到生成的新对象的。
  4. 通过 new 创建的每个对象将最终被[[Prototype]]链接到这个函数的 prototype 对象上。
  5. 如果函数没有返回对象类型 Object(包含 Functoin, Array, Date, RegExg, Error),那么 new 表达式中的函数调用会自动返回这个新的对象。
    Function.prototype.myBind = function () {
        if (typeof this !== 'function') {
            throw new TypeError(this + ' must be a function');
        }
        let _this = this;
        let _thisArg = arguments[0];
        let args = Array.prototype.slice.call(arguments, 1);
        let bound = function () {
            // bind 返回的函数的参数转成数组
            let boundArgs = Array.prototype.slice.call(arguments);
            // 连接 bind 函数返回参数与被 bind 函数参数
            let funcArgs = args.concat(boundArgs);
            // new 调用时(此处使用 this instanceof bound 不是很准确,new.target 就是解决这个问题的)
            if (this instanceof bound) {
                // 实现 1、2、4 步
                // _this 可能是箭头函数,箭头函数没有 prototype,所以不需要做指向操作
                if (_this.prototype) {
                    function fn() {}
                    fn.prototype = _this.prototype;
                    bound.prototype = new fn();
                }
                // 实现 3 步
                let result = _this.apply(this, funcArgs);
                // 实现 5 步
                let isObject = typeof result === 'object' && result !== null;
                let isFunction = typeof result === 'function';
                if (isObject || isFunction) {
                    return result;
                }
                return this;
            } else {
                return _this.apply(_thisArg, funcArgs);
            }
        };
        // Object.defineProperties 实现 Function.name 和 Function.length
        Object.defineProperties(bound, {
            name: {
                value: 'bound ' + _this.name,
            },
            length: {
                value: _this.length,
            },
        });
        return bound;
    };
    

es5-shim 源码实现 bind

var $Array = Array;
var ArrayPrototype = $Array.prototype;
var $Object = Object;
var array_push = ArrayPrototype.push;
var array_slice = ArrayPrototype.slice;
var array_join = ArrayPrototype.join;
var array_concat = ArrayPrototype.concat;
var $Function = Function;
var FunctionPrototype = $Function.prototype;
var apply = FunctionPrototype.apply;
var max = Math.max;
// 简版,源码更复杂
var isCallable = function isCallable(value) {
    if (typeof value !== 'function') {
        return false;
    }
    return true;
};
var Empty = function Empty() {};
// 源码使用的是 defineProperties,此处直接使用 prototype
FunctionPrototype.bind3 = function bind(that) {
    var target = this;
    if (!isCallable(target)) {
        throw new TypeError(
            'Function.prototype.bind called on incompatible ' + target
        );
    }
    var args = array_slice.call(arguments, 1);
    var bound;
    var binder = function () {
        if (this instanceof bound) {
            var result = apply.call(
                target,
                this,
                array_concat(args, array_slice.call(arguments))
            );
            if ($Object(result) === result) {
                return result;
            }
            return this;
        } else {
            return apply.call(
                target,
                that,
                array_concat.call(args, array_slice.call(arguments))
            );
        }
    };
    var boundLength = max(0, target.length - args.length);
    var boundArgs = [];
    for (var i = 0; i < boundLength; i++) {
        array_push.call(boundArgs, '$' + i);
    }
    // 使用 Function 构造方式动态生成形参 length $1,$2,$3...
    bound = $Function(
        'binder',
        'return function (' +
            array_join.call(boundargs, ',') +
            '){return binder.apply(this, arguments); }'
    )(binder);
    if (target.prototype) {
        Empty.prototype = target.prototype;
        bound.prototype = new Empty();
        Empty.prototype = null;
    }
    return bound;
};
Last Updated:
Contributors: af, zhangfei