Function.prototype.bind()

bind() 方法是 Function 实例的一个方法,它创建一个新的函数,当该函数被调用时,会调用此函数,并将 this 关键字设置为提供的值,以及在调用新函数时提供的给定参数序列。

试一试

语法

js
bind(thisArg)
bind(thisArg, arg1)
bind(thisArg, arg1, arg2)
bind(thisArg, arg1, arg2, /* …, */ argN)

参数

thisArg

当绑定函数被调用时,将作为 this 参数传递给目标函数 func 的值。如果函数不在 严格模式 下,nullundefined 将被全局对象替换,并且原始值将被转换为对象。如果使用 new 运算符构造绑定函数,则该值将被忽略。

arg1, …, argN 可选

在调用 func 时,要添加到传递给绑定函数的参数之前的参数。

返回值

具有指定的 this 值和初始参数(如果提供)的给定函数的副本。

描述

bind() 函数创建一个新的绑定函数。调用绑定函数通常会导致其包装的函数(也称为目标函数)的执行。绑定函数会将其传递的参数(包括 this 的值和前几个参数)存储为其内部状态。这些值是预先存储的,而不是在调用时传递的。通常,您可以将 const boundFn = fn.bind(thisArg, arg1, arg2) 看作等效于 const boundFn = (...restArgs) => fn.call(thisArg, arg1, arg2, ...restArgs) 的效果(但在构建 boundFn 时则不相同)。

可以通过调用 boundFn.bind(thisArg, /* more args */) 来进一步绑定绑定函数,这将创建另一个绑定函数 boundFn2。新绑定的 thisArg 值将被忽略,因为 boundFn2 的目标函数(即 boundFn)已经具有绑定的 this。当调用 boundFn2 时,它将调用 boundFn,而 boundFn 又会调用 fnfn 最终接收到的参数按顺序为:由 boundFn 绑定的参数、由 boundFn2 绑定的参数以及 boundFn2 接收到的参数。

js
"use strict"; // prevent `this` from being boxed into the wrapper object

function log(...args) {
  console.log(this, ...args);
}
const boundLog = log.bind("this value", 1, 2);
const boundLog2 = boundLog.bind("new this value", 3, 4);
boundLog2(5, 6); // "this value", 1, 2, 3, 4, 5, 6

如果目标函数是可构造的,则也可以使用 new 运算符构造绑定函数。这样做就像目标函数被构造了一样。预先附加的参数照常提供给目标函数,而提供的 this 值将被忽略(因为构造准备了自己的 this,如 Reflect.construct 的参数所示)。如果直接构造绑定函数,则 new.target 将是目标函数。(也就是说,绑定函数对 new.target 是透明的)。

js
class Base {
  constructor(...args) {
    console.log(new.target === Base);
    console.log(args);
  }
}

const BoundBase = Base.bind(null, 1, 2);

new BoundBase(3, 4); // true, [1, 2, 3, 4]

但是,由于绑定函数没有 prototype 属性,因此它不能用作 extends 的基类。

js
class Derived extends class {}.bind(null) {}
// TypeError: Class extends value does not have valid prototype property undefined

当使用绑定函数作为 instanceof 的右侧时,instanceof 将访问目标函数(存储在绑定函数内部)并读取其 prototype

js
class Base {}
const BoundBase = Base.bind(null, 1, 2);
console.log(new Base() instanceof BoundBase); // true

绑定函数具有以下属性

length

目标函数的 length 减去正在绑定的参数数量(不包括 thisArg 参数),最小值为 0。

name

目标函数的 name 加上 "bound " 前缀。

绑定函数还继承了目标函数的 原型链。但是,它没有目标函数的其他自身属性(例如,如果目标函数是类,则没有 静态属性)。

示例

创建绑定函数

bind() 最简单的用法是创建一个函数,无论如何调用它,都会使用特定的 this 值来调用它。

对于新的 JavaScript 程序员来说,一个常见的错误是从对象中提取方法,然后稍后调用该函数并期望它使用原始对象作为其 this(例如,在基于回调的代码中使用该方法)。

但是,如果没有特别注意,原始对象通常会丢失。使用原始对象从函数创建绑定函数可以很好地解决此问题。

js
// Top-level 'this' is bound to 'globalThis' in scripts.
this.x = 9;
const module = {
  x: 81,
  getX() {
    return this.x;
  },
};

// The 'this' parameter of 'getX' is bound to 'module'.
console.log(module.getX()); // 81

const retrieveX = module.getX;
// The 'this' parameter of 'retrieveX' is bound to 'globalThis' in non-strict mode.
console.log(retrieveX()); // 9

// Create a new function 'boundGetX' with the 'this' parameter bound to 'module'.
const boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81

注意:如果在 严格模式 下运行此示例,则 retrieveXthis 参数将绑定到 undefined 而不是 globalThis,导致 retrieveX() 调用失败。

如果在 ECMAScript 模块中运行此示例,则顶层 this 将绑定到 undefined 而不是 globalThis,导致 this.x = 9 赋值失败。

如果在 Node CommonJS 模块中运行此示例,则顶层 this 将绑定到 module.exports 而不是 globalThis。但是,retrieveXthis 参数在非严格模式下仍将绑定到 globalThis,在严格模式下绑定到 undefined。因此,在非严格模式(默认)下,retrieveX() 调用将返回 undefined,因为 this.x = 9 写入的对象与 getX 读取的对象(globalThis)不同(module.exports)。

事实上,一些内置的“方法”也是返回绑定函数的 getter — 一个著名的例子是 Intl.NumberFormat.prototype.format(),当访问时,它返回一个绑定函数,您可以直接将其作为回调传递。

部分应用函数

bind() 的下一个最简单的用法是创建一个具有预先指定的初始参数的函数。

这些参数(如果有)位于提供的 this 值之后,然后插入到传递给目标函数的参数的开头,然后是调用绑定函数时传递给它的任何参数。

js
function list(...args) {
  return args;
}

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

console.log(list(1, 2, 3)); // [1, 2, 3]

console.log(addArguments(1, 2)); // 3

// Create a function with a preset leading argument
const leadingThirtySevenList = list.bind(null, 37);

// Create a function with a preset first argument.
const addThirtySeven = addArguments.bind(null, 37);

console.log(leadingThirtySevenList()); // [37]
console.log(leadingThirtySevenList(1, 2, 3)); // [37, 1, 2, 3]
console.log(addThirtySeven(5)); // 42
console.log(addThirtySeven(5, 10)); // 42
// (the last argument 10 is ignored)

与 setTimeout() 一起使用

默认情况下,在 setTimeout() 中,this 关键字将设置为 globalThis,在浏览器中为 window。在使用需要 this 指向类实例的类方法时,您可以显式地将 this 绑定到回调函数,以保持实例。

js
class LateBloomer {
  constructor() {
    this.petalCount = Math.floor(Math.random() * 12) + 1;
  }
  bloom() {
    // Declare bloom after a delay of 1 second
    setTimeout(this.declare.bind(this), 1000);
  }
  declare() {
    console.log(`I am a beautiful flower with ${this.petalCount} petals!`);
  }
}

const flower = new LateBloomer();
flower.bloom();
// After 1 second, calls 'flower.declare()'

您也可以为此目的使用 箭头函数

js
class LateBloomer {
  bloom() {
    // Declare bloom after a delay of 1 second
    setTimeout(() => this.declare(), 1000);
  }
}

用作构造函数的绑定函数

绑定函数可以自动与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'

// The thisArg's value doesn't matter because it's ignored
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

请注意,您无需执行任何特殊操作即可创建用于与new一起使用的绑定函数。new.targetinstanceofthis等都按预期工作,就像构造函数从未被绑定一样。唯一的区别是它不能再用于extends

推论是,您无需执行任何特殊操作即可创建要普通调用的绑定函数,即使您希望绑定函数只能使用new来调用。如果您在没有new的情况下调用它,则绑定的this突然不再被忽略。

js
const emptyObj = {};
const YAxisPoint = Point.bind(emptyObj, 0 /*x*/);

// Can still be called as a normal function
// (although usually this is undesirable)
YAxisPoint(13);

// The modifications to `this` is now observable from the outside
console.log(emptyObj); // { x: 0, y: 13 }

如果您希望将绑定函数限制为只能使用new调用,或只能在不使用new的情况下调用,则目标函数必须强制执行该限制,例如通过检查new.target !== undefined或使用来实现。

绑定类

在类上使用bind()保留了类的大部分语义,除了当前类的所有静态自身属性都会丢失。但是,由于原型链被保留,您仍然可以访问从父类继承的静态属性。

js
class Base {
  static baseProp = "base";
}

class Derived extends Base {
  static derivedProp = "derived";
}

const BoundDerived = Derived.bind(null);
console.log(BoundDerived.baseProp); // "base"
console.log(BoundDerived.derivedProp); // undefined
console.log(new BoundDerived() instanceof Derived); // true

将方法转换为实用函数

bind()在您希望将需要特定this值的方法转换为接受先前this参数作为普通参数的普通实用函数时也很有用。这类似于通用实用函数的工作方式:而不是调用array.map(callback),您可以使用map(array, callback),这允许您将map与不是数组的类数组对象(例如arguments)一起使用,而无需修改Object.prototype

例如,以Array.prototype.slice()为例,您希望将其用于将类数组对象转换为真正的数组。您可以创建这样的快捷方式

js
const slice = Array.prototype.slice;

// ...

slice.call(arguments);

请注意,您不能保存slice.call并将其作为普通函数调用,因为call()方法也会读取其this值,该值是它应该调用的函数。在这种情况下,您可以使用bind()来绑定call()this值。在以下代码段中,slice()Function.prototype.call()的绑定版本,其this值绑定到Array.prototype.slice()。这意味着可以消除额外的call()调用。

js
// Same as "slice" in the previous example
const unboundSlice = Array.prototype.slice;
const slice = Function.prototype.call.bind(unboundSlice);

// ...

slice(arguments);

规范

规范
ECMAScript 语言规范
# sec-function.prototype.bind

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅