1.4.5 bind

创建一个新的绑定函数,其 this 值永久绑定到提供的对象上

ECMAScript 5 引入了 Function.prototype.bind()

调用 fn.bind(someObject) 会创建一个与 fn 具有相同函数体和作用域的新函数,但是对原函数体中的所有 this 值,新函数都会“永久绑定”到 bind() 的第一个参数上,而不管新函数的调用方式。

  • 新函数,a new bound function,一个新的绑定函数

  • 新函数的 this 值会“永久绑定”到第一个参数上,这意味着 bind only works once!

const module = {
    x: 33,
    getX: function () {
        return this.x;
    }
};

const unboundGetX = module.getX;
console.log(unboundGetX());

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());

以上代码的执行结果,如下:

undefined
33

1. bind() 函数

1.1 语法

bind(thisArg);
bind(thisArg, arg1, /* …, */ argN);
  1. 参数 thisArg:传给目标函数的 this 值,当新的绑定函数被调用的时候使用

    • 在非严格模式下,null 和 undefined 会被替换成全局对象,原始值会被转换成相应的对象

    • 如果绑定函数是用 new 运算符创建的,则会忽略该参数

  2. 参数 arg1, …, argN(可选):添加到 arguments 之前的参数

  3. 返回值:具有指定 this 值和初始参数(如果提供的话)的给定函数的一个拷贝

bind() 函数会创建一个新的绑定函数(a new bound function),调用该绑定函数通常会导致其包装函数的执行。绑定函数会把传过来的参数都存下来(包括 this 值和前几个参数)作为它的内部状态,这些值都是预先存好的,而不是在调用的时候才传递。

// 以下两种写法是等价的
const boundFn = fn.bind(thisArg, arg1, arg2);
const boundFn = (...restArgs) => fn.call(thisArg, arg1, arg2, ...restArgs);

1.2 新的绑定函数

function f() {
    return this.a;
}

const f1 = f.bind({ a: "bind1" });
console.log(f1());

const f2 = f.bind({ a: "bind2" });
console.log(f2());

const o = { a: "字面量对象", f, f1, f2 };
console.log(o.a, o.f(), o.f1(), o.f2());

以上代码的执行结果,如下:

bind1
bind2
字面量对象 字面量对象 bind1 bind2

1.3 只绑定一次

bind only works once,只绑定一次。

function f() {
    return this.a;
}

const f1 = f.bind({ a: "bind1" });
console.log(f1());

const f1_1 = f1.bind({ a: "bind1-1" });
console.log(f1_1());

const o = { a: "字面量对象", f, f1, f1_1 };
console.log(o.a, o.f(), o.f1(), o.f1_1());

以上代码的输出结果,如下:

bind1
bind1
字面量对象 字面量对象 bind1 bind1

2. 常见用法

2.1 创建绑定函数

bind() 最简单的用法就是创建一个绑定特定 this 值的函数,而不管它的调用方式。

新手的一个常见错误就是,先把对象中的方法在变量里存起来,然后通过该变量来执行那个方法,并期待方法里的 this 值还能指向原来的对象。比如:

this.x = 'outer';
const module = {
    x: 'inner',
    getX() {
        return this.x;
    }
};
console.log(module.getX());

const retrieveX = module.getX;
console.log(retrieveX());

以上代码的执行结果,如下:

inner
outer

要想让中间变量 retrieveX 在执行的时候能依然访问到原始的 module 对象,可以给它创建一个绑定到 module 对象的绑定函数。如下:

const boundGetX = retrieveX.bind(module);
console.log(boundGetX());

2.2 预先指定参数

bind() 的另一个简单用法是,在创建函数的时候,可以预先指定初始参数。比如:

function list(...args) {
    console.log(args);
    return args;
}
const list1 = list(1, 2, 3);
const leading33List = list.bind(null, 33);
const list2 = leading33List();
const list3 = leading33List(11, 22);

以上代码的执行结果,如下:

function addTowArguments(arg1, arg2) {
    let sum = arg1 + arg2;
    console.log(sum);
    return sum;
}
const result1 = addTowArguments(1, 2);
const add35 = addTowArguments.bind(null, 35);
const result2 = add35(3);
const result3 = add35(4, 5);

以上代码的执行结果,如下:

3
38
39

2.3 创建快捷方式

bind() 也能用于创建需要特定 this 值的函数的快捷方式。

比如想用 Array.prototype.slice() 把类数组对象转换成真正的数组,我们可以创建快捷方式:

const slice = Array.prototype.slice;
// ...
slice.apply(arguments);

使用 bind() 可以简化为:

const unboundSlice = Array.prototype.slice;
const slice = Function.prototype.apply.bind(unboundSlice);
// ...
slice(arguments);

在上面的代码中,slice()Function 对象的实例方法 apply() 的绑定函数,它将 this 值设置成了 Array 对象的实例方法 slice(),从而消除了额外的 apply() 调用。

2.4 setTimeout()

setTimeout() 中,this 值默认是 windowglobal 对象。

在类的方法中,this 值要么是类实例要么是类本身。当在类方法中使用 setTimeout() 时,如果要想让其回调里的 this 也指向类实例或类本身,就需要显式地绑定 this 值。比如:

class LateBloomer {
    constructor() {
        this.petalCount = 4;
    }
    bloom() {
        setTimeout(this.say.bind(this), 1000);
    }
    say() {
        console.log(`我有 ${this.petalCount} 个花瓣`);
    }
}

const flower = new LateBloomer();
flower.bloom();

2.5 用作构造函数

这部分是 bind() 用法的边缘情况,不推荐在线上环境使用(因为有更好的处理方法)

绑定函数也可以通过 new 运算符来构造,这样做就好像目标函数已经被构造好了一样,此时提供的 this 值会被忽略,而前置参数会被正常地传递给模拟函数。

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

let YAxisPoint = Point.bind(null, 0 /*x*/);
// let YAxisPoint = Point.bind({}, 0 /*x*/); // 也可以

const p1 = new YAxisPoint(7);
console.log(p1.toString());
console.log(p1 instanceof Point);
console.log(p1 instanceof YAxisPoint);

const p2 = new YAxisPoint(30, 33);
console.log(p2.toString());
console.log(p2 instanceof Point);

以上代码的运行结果,如下:

0,7
true
true
0,30
true

3. 主要参考

Last updated