javascript 原生bind方法实现

bind方法可以用来给一个方法绑定上下文环境对象,以及重新给方法传参数。
bind的另一个简单使用是使一个函数拥有预设的初始参数。我们称为偏函数

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

由于bind方法在并不是在所有的浏览器上都支持,因此我们考虑自己实现bind方法。
首先我们可以给目标函数指定作用域来简单实现bind

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

这样实现的bind方法就只能接受一个上下文环境变量的参数,不能同时接受参数。因此我们修改一下。

Function.prototype.bind = function(context){
    var slice = Array.prototype.slice,
        _args = slice.call(arguments,1),
        self = this;
    return function(){
        var _inargs = slice.call(arguments);
        return self.apply(context, _args.concat(_inargs));
    }
}

现在bind可以绑定对象,同时也能在绑定对象时传递参数。
但是bind还有一个特点:

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
并看不懂什么意思 = = 其实就是bind返回的函数还能用做构造函数。bind 时指定的 this 值会失效,但传入的参数依然生效。

举个例子:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

我们可以通过修改函数的返回原型来实现,代码如下:

Function.prototype.bind = function(context){
    var slice = Array.prototype.slice,
        _args = slice.call(arguments,1),
        self = this,
        fBound = function(){
            var _inargs = slice.call(arguments);
            // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
            // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
            // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
            return self.apply((this instanceof fBound ? this : context), _args.concat(_inargs));
        };
        // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
        fBound.prototype = self.prototype;
        return fBound;
}

bound.prototype = this.prototype这么写的话,修改返回函数原型对象(bound.prototype)的同时把绑定函数的原型对象(this.prototype也同时修改了。因此用匿名函数做中转,this.protptype 就安全了。
还有几个小问题的解决:

  • 调用bind不是函数
  • bind兼容性的问题
    最终的完整代码如下:
fakeBind = function (context) {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var slice = Array.prototype.slice,
        _args = slice.call(arguments, 1),
        self = this,
        F = function () {},
        bound = function () {
            var _inargs = slice.call(arguments);
            return self.apply((this instanceof F ? this : context), _args.concat(_inargs));
        };
    F.prototype = self.prototype;
    bound.prototype = new F();
    return bound;
}

Function.prototype.bind = Function.prototype.bind || fakeBind;
ES6版实现
Function.prototype.fakeBindES6 = function(context, ...rest) {
  if (typeof this !== "function") {
    throw new Error("Bind must be called on a function");
  }
  var self = this;
  return function inner(...args) {
    if (this instanceof inner) {
      // 当返回的内层函数作为构造函数使用,bind 时绑定的 this 失效。
      // 即此处直接执行绑定函数,而不使用 apply 对 this 进行绑定 
      return new self(...rest, ...args);
    }
    // 当作为普通函数调用,this 指向传入的对象
    return self.apply(context, rest.concat(args));
  };
};

Github: https://github.com/Vxee/articles/issues/7
posted @ 2018-07-21 14:24  vxee  阅读(142)  评论(0编辑  收藏  举报