call / apply / bind
对于 call / apply / bind 来说,他们的首要目的是用于改变执行上下文的 this 指针。
call / apply
对 call / apply 的使用,一般都如下,用于改变执行环境的上下文。只是 call 接受的是一个一个的参数,而 apply 则是接受的是一个参数数组。
const obj1 = {
a: 1,
myFunc(var1) {
console.log(this.a + var1)
}
}
const obj2 = {
a: 2
}
const myFunc = obj1.myFunc
myFunc(1) // NaN
obj1.myFunc(1) // 2
myFunc.call(obj2, 1) // 3
myFunc.apply(obj2, [1]) // 3
bind
bind 是 ES2015 出的一个方法,也是用于改变函数内部的 this 指向。但不一样的是,bind 方法不是直接执行的,而是生成一个新的已被改变过的函数。
const obj1 = {
a: 1,
myFunc(var1) {
console.log(this.a + var1)
}
}
const obj2 = {
a: 2
}
const myFunc = obj1.myFunc
const bindMyFunc1 = myFunc.bind(obj1)
const bindMyFunc2 = myFunc.bind(obj2)
myFunc(1) // NaN
bindMyFunc1(1) // 2
bindMyFunc2(1) // 3
通过上面的例子就可以看出来,bind 方法就可以生成一个新的 this 指向的 function。
手动写 bind 函数
仅仅作为简单实现的话,我们仅需要注意改变 this 指向和预置参数即可。
function bind(fn, _this, ...args) {
if(typeof fn !== 'function') {
throw new Error('bind fn need to be function')
}
return function(...innerArgs) {
return fn.apply(_this, [...args, ...innerArgs])
}
}
当然这个手动实现的 bind 方法是只实现了最主要的功能,对函数的原型链和作为构造函数的方式都是没有考虑到的。这里可以参考 MSDN 的 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() {
// 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 doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}