Skip to content

bind 模拟实现 ⭐️

bind 的效果

js
function fn(a, b, c, d) {
  console.log(a, b, c, d);
  console.log(this);
}

const newFn = fn.bind({ newThis: '整个对象是新的this' }, 1, 2);
newFn(3, 4);
// 1 2 3 4
// { newThis: '整个对象是新的this' }

bind 会改变函数的 fnthis 指向, 在上面的例子中, 就被修改为对象 { newThis: '整个对象是新的this' }

在对原函数 fn 使用 bind 时, 可以传入参数 1 2 作为初始参数, 新函数 newFn 调用的时候再传入其余的参数

基础实现

js
// myBind 的第一个参数 ctx 为 this
Function.prototype.myBind = function (ctx) {
  // 这里获得其他不定量参数, 例如上面例子中的 1, 2, 于是 args = [1, 2]
  var args = Array.prototype.slice.call(arguments, 1);
  // 使用形式: fn.myBind, 原始函数为fn, 就是此 myBind 函数的 this 指向
  var fn = this;
  return function () {
    // 这里获取 经过 bind 处理后返回的新函数, 在调用时的参数
    // 例如上面例子中的 3, 4, 于是 restArgs = [3, 4]
    var restArgs = Array.prototype.slice.call(arguments);
    // 所有的参数 例如上面例子, 于是allArgs = [1, 2, 3, 4]
    var allArgs = args.concat(restArgs);
    // 调用原始函数, this 指向新的 ctx; 添加 return, 因为原函数可能有返回值
    return fn.apply(ctx, allArgs);
  };
};

测试用例

js
function fn(a, b, c, d) {
  console.log(a, b, c, d);
  console.log(this);
  return 77777;
}

const newFn = fn.myBind({ newThis: '整个对象是新的this' }, 1, 2);
const value = newFn(3, 4);
console.log(value);
// 1 2 3 4
// { newThis: '整个对象是新的this' }
// 77777

兼容 new 关键词 ⭐️

这里是个难点, 参考 MDN, 默认的 bind 有如下的行为:

绑定函数自动适用于与 new 运算符一起使用,以用于构造目标函数创建的新实例。当使用绑定函数是用来构造一个值时,提供的 this 会被忽略。然而,提供的参数仍会被插入到构造函数调用时的参数列表之前。

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

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

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

// thisArg 的值并不重要,因为它被忽略了
const YAxisPoint = Point.bind(null, 0 /*x*/);

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

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new YAxisPoint(17, 42) instanceof Point; // true

下面是模拟实现:

js
// myBind 的第一个参数 ctx 为 this
Function.prototype.myBind = function (ctx) {
  // 这里获得其他不定量参数, 例如上面例子中的 1, 2, 于是 args = [1, 2]
  var args = Array.prototype.slice.call(arguments, 1);
  // 使用形式: fn.myBind, 原始函数为fn, 就是此 myBind 函数的 this 指向
  var fn = this;
  // 给函数取名, 用于判断是否在用 new 调用
  return function newFunctionName() {
    // 这里获取 经过 bind 处理后返回的新函数, 在调用时的参数
    // 例如上面例子中的 3, 4, 于是 restArgs = [3, 4]
    var restArgs = Array.prototype.slice.call(arguments);
    // 所有的参数 例如上面例子, 于是allArgs = [1, 2, 3, 4]
    var allArgs = args.concat(restArgs);
    // 调用原始函数, this 指向新的 ctx; 添加 return, 因为原函数可能有返回值

    // 如果是使用 new 的方式调用函数
    if (Object.getPrototypeOf(this) === newFunctionName.prototype) {
      return new fn(...allArgs);
    } else {
      return fn.apply(ctx, allArgs);
    }
  };
};

测试用例

js
function fn(a, b, c, d) {
  console.log(a, b, c, d);
  console.log(this);
  return 77777;
}

const newFn = fn.myBind({ newThis: '整个对象是新的this' }, 1, 2);
const value = new newFn(3, 4);
console.log(value);
// 1 2 3 4
// fn {}
// fn {}

输出跟将 fn.myBind 替换为 fn.bind的默认行为是一致的

参考链接

手写 bind@必修课 DDD