JavaScript 系列 - Function.prototype.bind

173 阅读3分钟

概念

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

  • thisArg

    • 调用绑定函数时作为 this 参数传递给目标函数的值
    • 如果使用 new 运算符构造绑定函数,则忽略该值
    • 当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object
    • 如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg
  • arg1, arg2, ...

    当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

使用示例

创建绑定函数

this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function () {
    return this.x;
  },
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

偏函数

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

function addArguments(arg1, arg2) {
  return arg1 + arg2;
}

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

var result1 = addArguments(1, 2); // 3

// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);

// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);

var list2 = leadingThirtysevenList();
// [37]

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

var result2 = addThirtySeven(5);
// 37 + 5 = 42

var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42,第二个参数被忽略

配合 setTimeout

在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window(或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function () {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function () {
  console.log("I am a beautiful flower with " + this.petalCount + " petals!");
};

var flower = new LateBloomer();
flower.bloom(); // 一秒钟后,调用 'declare' 方法

作为构造函数使用的绑定函数

  • 不应该用在任何生产环境中
  • 当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略
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*/);

// 本页下方的 polyfill 不支持运行这行代码,
// 但使用原生的 bind 方法运行是没问题的:

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

/*(译注:polyfill 的 bind 方法中,如果把 bind 的第一个参数加上,
即对新绑定的 this 执行 Object(this),包装为对象,
因为 Object(null) 是 {},所以也可以支持)*/

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

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

快捷调用

在你想要为一个需要特定的 this 值的函数创建一个捷径(shortcut)的时候,bind() 也很好用。

var slice = Array.prototype.slice;
slice.apply(arguments);
// 相同
var slice = Function.prototype.apply.bind(slice);
slice(arguments);

模拟

Function.prototype.myBind = function (context) {
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  var _this = this;
  // args 是 bind 函数传递的参数
  var args = Array.prototype.slice.call(arguments, 1);
  return function F() {
    // bindArgs 是 F 函数的参数
    var bindArgs = Array.prototype.slice.call(arguments);
    if (this instanceof F) {
      var t = [];
      for (var i = 0, len1 = args.length; i < len1; i++) {
        t.push("args[" + i + "]");
      }
      for (var j = 0, len2 = arguments.length; j < len2; j++) {
        t.push("arguments[" + j + "]");
      }
      return eval("new _this(" + t + ")");
    }
    return _this.apply(context, args.concat(bindArgs));
  };
};

Function.prototype.myBind = function (context) {
  if (typeof this !== "function") {
    console.error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  const _this = this;
  // args 是 bind 函数传递的参数
  const args = [...arguments].slice(1);

  return function F() {
    if (this instanceof F) {
      // arguments 是 F 函数的参数
      return new _this(...args, ...arguments);
    }
    return _this.apply(context, [...args, ...arguments]);
  };
};