yliu

时来天地皆同力,运去英雄不自由

模拟实现bind


之前说了模拟实现 call 和 apply,下面就来实现 bind,首先先看一下定义 bind 定义

bind()方法创建一个新的函数,在 bind()被调用时,这个新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

第一版

根据定义我们尝试写一下

Function.prototype.binds = function(con) {
  var fn = this;
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    return fn.apply(con, args);
  };
};

我们来测试一下上面的函数

var obj = {
  value: "zhangsan"
};
function foo(name, age) {
  console.log(this.value); //zhangsan
  console.log(name); // name
  console.log(age); // undefined
}
var f = foo.binds(obj, "name");
f(19);

age 输出的跟我们预期的不太一致,这是因为我们没有处理返回函数的参数,用 concat 把参数合并在一起就可以了。

Function.prototype.binds = function(con) {
  var fn = this;
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    return fn.apply(con, args.concat(Array.prototype.slice.call(arguments)));
  };
};

第二版

上面简单实现了函数的 bind,不过我们来看一下 bind 与构造函数相遇会怎么样

var obj = {
  value: "zhangsan"
};
function Foo(name, age) {
  console.log(this.value); //undefined
  console.log(name);
  console.log(age);
}
var f = Foo.bind(obj, "name");
new f(19);

上面 this.value 的输出为 undefined,这是因为 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用 new 运算符构造绑定函数,则忽略该值

另外作为构造函数是可以获取到绑定 bind 函数的 prototype

function Foo() {
  console.log(this.gender); //male
}
Foo.prototype.gender = "male";
var f = Foo.bind(null, "name");
new f();

下面就是来实现上面两点:

  • 怎么来判断是否通过 new 来调用呢?可以通过instanceof
function Foo() {
  if (!(this instanceof Foo)) {
    throw Error("请通过new调用");
  }
}
Foo();
  • 继承的话我们可以通过寄生组合式继承,这类文章比较多,这里就简单举一个例子
function create(con) {
  var f = function() {};
  f.prototype = con;
  return new f();
}
function Par(name) {
  this.name = name;
}
Par.prototype.getName = function() {
  return this.name;
};
function Chi(name, age) {
  Par.call(this, name);
  this.age = age;
}
Chi.prototype = create(Par.prototype);
Chi.prototype.constructor = Chi;
Chi.prototype.getDetails = function() {
  var n = this.getName();
  console.log(n, this.age);
};
var d = new Chi("zhangsan", 16);
d.getDetails();

OK,上面两点解决后我们再来实现一版

Function.prototype.binds = function(con) {
  var fn = this;
  var args = Array.prototype.slice.call(arguments, 1);
  var f = function() {};
  if (this.prototype) {
    f.prototype = this.prototype;
  }
  var result = function() {
    return fn.apply(
      this instanceof result ? this : con,
      args.concat(Array.prototype.slice.call(arguments))
    );
  };
  result.prototype = new f();
  return result;
};

优化

观察上面的不难发现 bind 调用的必须是函数,而通过继承我们可能会调用到 bind 所以这里显示报错一下

var obj = Object.create(Function.prototype);
console.log(obj.bind);

另外对于我们上面定义的binds肯定不太优雅,污染了命名空间,这里可以判断一下如果存在 bind 就用 bind 否则在定义

最终

if (!Function.prototype.bind) {
  Function.prototype.bind = function(con) {
    if (typeof this !== "function") {
      throw new TypeError(
        "Function.prototype.bind - what is trying to be bound is not callable"
      );
    }
    var fn = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var f = function() {};
    if (this.prototype) {
      f.prototype = this.prototype;
    }
    var result = function() {
      return fn.apply(
        this instanceof result ? this : con,
        args.concat(Array.prototype.slice.call(arguments))
      );
    };
    result.prototype = new f();
    return result;
  };
}