前言
在读这篇文章之前,希望你对Function.prototype.bind
有所了解。
如果还没有的话,强烈推荐去看看MDN上关于它的介绍,飞机票。
主要有以下两个特征:
- 多次bind,仅第一次的bind传入的绑定this生效
- 使用new 操作bind返回的构造函数,曾经绑定的this会失效
bind的polyfill
MDN上为了向下兼容给出了bind的polyfill,先把代码贴出来:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype does not have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
一段示例代码
var o1 = { a: 1 }
var o2 = { b: 2 }
var f = function () {
console.log(this)
console.log([].slice.call(arguments))
}
var f1 = f.bind(o1, 1, 2) // A行
var f2 = f1.bind(o2, 3, 4) // B行
f2(5, 6) // C行
学习方法有正向也有反向,我们从运行代码来解释这段polyfill
分析
接下来将会从执行上下文栈来解析这段代码运行的整个过程。 如果对“执行上下文栈”还不了解的话,推荐看我的另一篇文章——执行上下文
1. 刚开始时的全局执行上下文:
- 变量对象:o1,o2,f,f1,f2
- 作用域链:目前为空
- this,指向window
2. A行执行时加入的执行上下文:
- 变量对象:oThis === o1,aArgs === [1, 2],fToBind === f,fNOP,fBound
- 作用域链:全局执行上下文
- this,指向f
- 返回的f1,指向变量对象的fBound,它的原型链:fBound.prototype.proto === f.prototype
3. B行执行时加入的执行上下文:
- 变量对象:oThis === o2,aArgs === [3, 4],fToBind === f1,fNOP,fBound
- 作用域链:全局执行上下文
- this,指向f1
- 返回的f2,指向变量对象的fBound,它的原型链:fBound.prototype.proto === f1.prototype
4. C行执行时加入的执行上下文:
- 变量对象:arguments
- 作用域链:比较复杂,看下面说明
- this,指向window
C行其实会执行两次函数:
第一次:
- 变量对象:arguments === [5, 6]
- 作用域链:B行的执行上下文(闭包)、全局执行上下文
- this,指向window
f2(5, 6) === return f1.apply(o2, [3, 4, 5, 6])
第二次:
- 变量对象:arguments === [3, 4, 5, 6]
- 作用域链:A行的执行上下文(闭包)、全局执行上下文
- this,指向o2
return f1.apply(o2, [3, 4, 5, 6]) === return f.apply(o1, [1, 2, 3, 4, 5, 6]
5. 结果
所以f2(5, 6)的打印的结果就是
{a: 1}
[1, 2, 3, 4, 5, 6]
可以直接放到chrome的开发者工具里运行得到结果。
两处亮点
1. 维护原型关系
这里使用的是“原型式继承”,可以参考我的另一篇文章——类相关。
在这里的作用是,把原函数(f)的原型保留下来,以供第二个亮点使用。
2. bind不影响new
我想你一定很疑惑fBound里的这段代码
this instanceof fNOP ? this : oThis
其实这里的作用就是为了bind返回的函数不影响new操作符创建对象(也就是this被忽略)。
如果再执行以下语句,再上门的基础上修改f:
var f = function () {
this.c = 3
console.log(this)
console.log([].slice.call(arguments))
}
var f2Obj = new f2(5, 6);
// 运行过程,下面的this指将要创建的新对象:
f2(5, 6) === return f1.apply(this, [3, 4, 5, 6] === return f.apply(this, [1, 2, 3, 4, 5, 6]
// 结果(在chrome上执行)
打印:
f {c: 3}
[1, 2, 3, 4, 5, 6]
并且 f2Obj.c === 3
总结
不由得感叹这个polyfill的作者,思维太缜密了。我只能通过解析执行上下文一步一步来了解整个设计思路。
- 借助闭包保存每次bind传入的参数,包括thisArg和args
- 返回的fBound形成调用链,每一个fBound都引用上一个fBound,尾端是原函数
- 使用原型式继承的方式使new操作符创建新对象时候不受曾经绑定的this的影响
谢谢你能看到这里。
原文摘自我的个人博客,欢迎进来踩踩。