ECMAScript 5 引入了 Function.prototype.bind()
。
调用 fn.bind(someObject)
会创建一个与 fn
具有相同函数体和作用域的新函数,但是对原函数体中的所有 this
值,新函数都会“永久绑定”到 bind()
的第一个参数上,而不管新函数的调用方式。
新函数,a new bound function,一个新的绑定函数
const module = {
x: 33,
getX: function () {
return this.x;
}
};
const unboundGetX = module.getX;
console.log(unboundGetX());
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
以上代码的执行结果,如下:
1. bind() 函数
1.1 语法
bind(thisArg);
bind(thisArg, arg1, /* …, */ argN);
参数 thisArg
:传给目标函数的 this
值,当新的绑定函数被调用的时候使用
在非严格模式下,null 和 undefined 会被替换成全局对象,原始值会被转换成相应的对象
如果绑定函数是用 new 运算符创建的,则会忽略该参数
参数 arg1, …, argN
(可选):添加到 arguments 之前的参数
返回值:具有指定 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());
以上代码的执行结果,如下:
要想让中间变量 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);
以上代码的执行结果,如下:
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
值默认是 window
或 global
对象。
在类的方法中,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. 主要参考