在面对 JS 面试题的时候,this 往往会成为困扰我们解题的绊脚石。比如下面的代码:
var object = {
fn: function(){
console.log('hi');
}
}
var a = object.fn;
object.fn(); //打印出的 this 是 object
a(); //打印出的 this 是 window
那该如何分辨出不同的函数执行的 this 呢?我们可以先从函数调用入手。
函数调用
对于 JS 中的函数调用,作为新手我们比较熟悉的就是以下两种形式:
funciton(a, b);
object.child.function(a, b);
但是正如一开始的面试题所呈现的那样,这两种形式的函数调用都没办法很直观的看出 this 的值。但以 ES5 为例,还有第三种我们不太熟悉的函数调用形式:
function.call(context, a, b);
当然,除了 call 语法,还有 apply 语法,我们先不讨论 apply
其实,前两种函数调用形式只是相当于 JS 提供的语法糖,它们可以随时等价地转换成 call 的形式:
funciton(a, b) <===> funciton.call(undefined, a, b)
object.child.function(a, b) <===> object.child.function.call(object.child, a, b)
那么通过转换成 call 形式的函数调用后,我们就可以清晰的看出 this 是什么:
this 就是 function.call(context, a, b) 中的 context !!!
所以其实每一次我们在调用函数的时候,其实都会传入 this ,即 context 。只不过大多数时候 JS 通过语法糖的形式帮我们简化了写法。所以当我们需要弄清楚 this 是什么的时候,我们只需要把函数改写成 call 形式,那么 this 就会显而易见了。
但是还是会有奇怪的事情发生。
比如我们看下面的代码:
function fn(){
console.log(this);
}
fn();
我们把它转换成 call 形式为:
function fn(){
console.log(this);
}
fn.call(undefined);//可简化成 fn.call()
那么按照我们上面的理论,此时浏览器应该会直接打出 undefined ,因为 this 应该是 undefined 。但是浏览器自身会有一条规则:
如果传进去的 context 为 null 或者 undefined ,那么浏览器会将 window 对象作为默认的 context,除非是严格模式下,context 才会保持 undefined
那么上面的函数打印出来的结果应该是 window。
那么对于 object.child.fn(a, b) 的 this
var object = {
fn: function(){
console.log(this)
}
}
object.fn()
我们可以把它转换成下面的形式:
var object = {
fn: function(){
console.log(this)
}
}
object.fn.call(object) //this 就是 object
而对于 [] 形式的函数调用:
function fn(){
console.log(this)
}
var array = [fn1, fn2];
array[0]();
可以转换成
array.0.call(array)//虽然形式不正确,但是意思是这个意思
那么就可以看到 this 是 array 了。
箭头函数
有意思的是,当我们讨论箭头函数 ()=>{}时,会发现箭头函数是没有 this 的,如果一些代码的箭头函数中有 this,那么它肯定是这个箭头函数外面的 this。
总结
- this 就是以 call 形式的函数调用时所传入的 context,即第一个参数。
- 如果函数调用不是 call 形式,那么可以将其转换成 call 形式的函数调用,这样有助于我们辨别 this。
以上总结参考了方应杭老师的《this 的值到底是什么?一次说清楚》。