今天面试今日头条,被问到这样一段代码
function func(...args) {
console.log(this, ...args)
}
func.bind({a:1}).bind({b: 1})(2,3)
结果是:
{a: 1} 2 3
为什么呢?MDN中给出的Polyfill是这样的, 虽然和标准实现有些差别,但基本的功能是一样的,我们可以用它来了解bind的实现原理:
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');
}
// 有朋友表示不知道这里为什么这样写,其实原因是
// arguments是一个类数组,它本身看起来像一个数组,但是它不没有数组的方法
// 那么这里我们要取出arguments里第一个参数往后的参数的话,就需要给这个arguments赋予一个slice方法
// Array.prototype.slice.call(arguments)就是当slice执行时,this指向了arguments,
// 就相当于arguments.slice()
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
console.log('this', this);
console.log('oThis', oThis);
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// 当执行Function.prototype.bind()时, this为Function.prototype
// this.prototype(即Function.prototype.prototype)为undefined
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}
执行
function test(...args) {
console.log(this, ...args);
}
test.bind({a:1}).bind({b: 1})(2,3);
可以看到控制台的打印:
根据这个打印结果以及bind的实现,我们可以看到,bind的实现方式是一个柯里化的函数,每次的绑定就相当于:
function test(...args) {
console.log(this, ...args);
}
function f1() {
return test.apply({a: 1})
}
function f2() {
return f1.apply({b: 1})
}
f2()
把f1换成第一次bind,f2换成第二次bind,就可以明白为什么最后只有第一个bind生效,因为执行到最后时,传进去的oThis就是第一次绑定的对象。
ps:关于函数的柯里化如何实现,我还没有太理解,希望下次能明白