本文用于总结 JavaScript bind 函数的机制,分析其源码以及应用。
介绍
根据MDN上的介绍:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
就是说 bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
bind 应用
创建硬绑定的this
使用MDN上的例子:
1  | this.x = 9; // this refers to global "window" object here in the browser  | 
可以看到,在一个函数上使用bind,其在调用时的this始终会指向给bind传递的第一个参数,这就是硬绑定。
硬绑定还可以用于setTimeout、事件处理函数。在setTimeout中,this的值默认是 window,事件处理函数中的this通常情况下是事件发生的目标DOM节点。在很多时候不注意就会产生 BUG,此时就可以使用 bind 来显示指定this的值。MDN例子:
1  | function LateBloomer() {  | 
如果写成
1  | window.setTimeout(this.declare, 1000);  | 
那么运行declare函数时,this的值指向window,而window中没有petalCount,就会报错。
bind在事件处理函数中的应用与setTimeout类似,这里就不赘述了。
函数柯里化
柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
也可以理解为如果一个函数接收多个参数,那么柯里化会把函数的前几个参数固定为特定的值,之后的参数会在调用时再指定。还是来看MDN上的例子
1  | function list() {  | 
可以看到,list函数在没有使用柯里化时,所有的参数都需要在调用时指定。在进行柯里化之后的leadingThirtysevenList函数,其第一个参数被固定为了37,在真正调用leadingThirtysevenList时传递的参数都会在这个37之后。
这里注意,调用bind时,若第一个参数是null,并不是说柯里化之后的函数的this被绑定为了null,而是undefined或者window,取决于是否在严格模式下。
延迟执行
这是参考的前端开发者进阶之函数柯里化 Currying这篇博客。
柯里化还可以用于延迟执行:不断的柯里化,累积传入的参数,最后执行。例如:
1  | var add = function() {  | 
通用的写法:
1  | var curry = function(fn) {  | 
源码分析
先贴上MDN上的源码:
1  | if (!Function.prototype.bind) {  | 
使用
1
if (!Function.prototype.bind)
是兼容不支持
bind的浏览器,bind是在 ES5 中才出现的,在较新的浏览器上都已经有了原生的bind实现,所以不需要使用垫片。函数最开始的判断是因为
bind只能在函数上调用,非函数上调用需要报错。aArgs是调用bind时参入的固定参数,可以看到最终的fBound在调用时的实参是综合了固定参数和后来调用fBound传递的参数,这就实现了柯里化。为什么不直接使
fBound的原型和this的原型相同,即1
fBound.prototype = this.prototype;
因为这样就会导致在改变
fBound时连带着改变了调用bind的那个函数,即fToBind。
所以使用一个空函数fNOP作为中转。此时原型链的示意为fBound.prototype -----> new fNOP() ---> this.prototypefToBind.apply(this instanceof fNOP? this: oThis, ...)的作用:
这是因为当bind返回的函数作为构造函数的时候,bind指定的this值会失效,此时的this会指向构造出来的那个对象实例。具体可以参见MDN给的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function 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'
// not supported in the polyfill below,
// works fine with native bind:
var YAxisPoint = Point.bind(null, 0 /*x*/);var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/x/);var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // ‘0,5’axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true
`
软绑定
此节内容参考你不懂 JS:this 豁然开朗
硬绑定 是一种通过强制函数绑定到特定的 this 上,来防止函数调用在不经意间退回到 默认绑定 的策略(除非你用 new 去覆盖它!)。问题是,硬绑定 极大地降低了函数的灵活性,阻止我们手动使用 隐式绑定 或后续的 明确绑定 尝试来覆盖 this。
为默认绑定 提供不同的默认值(不是 global 或 undefined),同时保持函数可以通过 隐式绑定 或 明确绑定 技术来手动绑定 this。
模拟代码:
这里提供的 softBind(..)工具的工作方式和 ES5 内建的 bind(..)工具很相似,除了我们的 软绑定 行为。他用一种逻辑将指定的函数包装起来,这个逻辑在函数调用时检查 this,如果它是 global 或 undefined,就使用预先指定的 默认值 (obj),否则保持 this 不变。它也提供了可选的柯里化行为。
来看看它的用法: