浅谈“this”(下)
问题
在上篇博客中,我们简单说了调用栈、默认绑定、隐式与显式绑定等几种规则,还记得文章末留下的问题嘛?
我们通过硬绑定还修改 this 的绑定位置,但是这存在着一个问题,一旦使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this 的指向, 函数的灵活性大大降低,作为一个有追求的码农,有没有一种办法既可以自定义 this 的指向,又可以动态的修改呢?
答案是有的,我们可以通过一种被称之为软绑定(我也不知道这名字谁取的)的方式来实现,回想一下我们前面提到的默认绑定,在独立函数调用时他会将 this 默认绑定到全局对象(严格模式下为undefined)上,思考一下,如果我们可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力,即称之为软绑定。
talk is cheap,上代码
if(!Function.prototype.softBind){
Function.prototype.softBind = function(obj){
var fn = this;
//捕获所有 curried 参数
var curried = [].slice.call( arguments ,1 )
var bound = function(){
return fn.apply(
(!this || this === (window || global))?
obj : this
curried.concat.apply( curried ,arguments )
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
}
上面的代码可能难得读懂,大致的意思就是:首先检查调用时的 this ,如果 this 绑定到全局对象或者undefined,那就把指定的默认对象绑定到this,否则不会修改this。 除了软绑定之外,softBind()的其他原理和ES5内置的bind()类似,他会对指定的函数进行封装, 下面我们来看看softBind是否实现了软绑定功能:
function foo(){
console.log("name" + this.name);
}
var obj = {name : "wanglang"},
var obj2 = {name : "heiguo"},
var obj3 = {name : "weige"};
var fooOBJ = foo.softBind(obj);
fooOBJ();// name : wanglang
obj2.foo =foo.softBind(obj);
obj2.foo(); // name : heiguo <-发现了吗 this被修改了
fooOBJ.call(obj3); // name : weige
setTimeout(obj2.foo,10);
//name : wanglang <----软绑定
从上面的代码我们可以看到,软绑定版本的foo()可以手动将this 绑定到obj2或者obj3上,但如果应用默认绑定,则会将this绑定到obj。
注意
上面我们说了关于this绑定的几种规则,但有一个特例,在ES6语法中的箭头函数是不适用于这些规则的,它的this指向是根据外层作用域来决定的。
举例说明:
function foo(){
//返回一个箭头函数
return (a) =>{
console.log(this.a);//this继承自foo()
}
}
var obj1 = {
a: 1
};
var obj2 = {
a:2
};
var bar = foo.call(obj1);
bar.call(obj2);//输出结果为1,而不是2
foo()内部创建的箭头函数会捕获调用时foo的 this,由于foo()的 this 绑定到obj1,bar(引用箭头函数)的 this 也会绑定到obj1,箭头函数的绑定是无法被修改的。
总结
通过这两篇博客,对 this 的指向或许有了一些新的认识,也让我巩固了一下前面学到的知识,果然,温故而知新。