关于this指向的极简解析,搞懂80%以上的指向问题

442 阅读3分钟

在刚入门开始学 this 的时候,我自己对 this 的指向是非常迷惑的,感觉总是抓不到重点。同时,对 this 指向的系统分析对于小白来说也很难搞懂。

在经过一段时间的总结后,大概弄明白了不同情况下 this 的指向,这里就来给大家总结一波,尽量用通俗简单的语言讲明白 this 的指向情况。

前置知识

this 是什么?

普遍概念:JavaScript 的关键字,当前环境执行上下文对象的属性。

能读懂的概念:this 就是你 call 一个函数时,传入的第一个参数。也可以理解成 this 就是 call 的第一个参数。

函数调用的3种形式

f(p1, p2); //等价于 function.call(undefined, p1, p2) 

obj.child.method(p1, p2);// 等价于obj.child.method.call(obj.child, p1, p2) 

f.call(context, p1, p2); // 这种调用形式,才是完整的

this 就是以上代码中的 context ,就是 call 一个函数时候传的 context

this指向的几种情况

在分析以下几种情况的时候,可以结合 fn.call() ,就会得出答案,不需要刻意去记忆。

第一种:f(p1, p2)

function f(){ console.log(this) } 
f()

//在此代码中,f() === f.call()

以上实例中,打印出来的结果为 window。为什么呢?因为浏览器规定:

如果你传的 context 是 null 或 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)

如果想改变这里 this 的值,可以使用如下代码:

f.call(obj) // 里面的this变成了obj

第二种:obj.child.method(p1,p2)

var obj = { foo: function(){ console.log(this) } } 
obj.foo()

obj.foo() 转换为 obj.foo.call(obj) ,这里的 this 即为 obj

第三种:包含[]

arr[0]()这里面的 this 又是什么呢?

function fn (){ console.log(this) } 
var arr = [fn, fn2] 
arr[0]()

可以把 arr[0] 假想成 arr.0() ,这个形式和前面提到的 obj.child.method(p1,p2) 类似,可以直接转换了。arr[0]() - arr.0() - arr.0.call(arr),易得 this === arr

第四种:箭头函数没有 this

箭头函数里并没有 this,如果你在箭头函数里看到 this,你直接把它当作箭头函数外面的 this 即可。外面的 this 是什么,箭头函数里面的 this 就还是什么,因为箭头函数本身不支持 this

第五种:EventHandler

在添加事件监听时,也会遇到需要获取 this 的情况:

button.addEventListener('click' ,function handler(){
  console.log(this)
})

通过前面的 call 方法不能直接判断 this 是什么,我们需要找到 handler 调用时的代码。查找 MDN 文档,可以看到这样一段描述:

当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。

转换成可以理解的代码如下:

handler.call(event.currentTarget, event) 

可以得出其中的 this 就是 event.currentTarget

常见面试题解析

通过以上方法,我们可以确定大部分情况下的 this 指向,这里放几道经典的面试题,来给大家巩固一下。

第一题

var a = {
  name:"里面的name",
  sayName: function(){
    console.log("this.name = " + this.name)
  }
}

var name = "外面的name";

function sayName(){
  var sss = a.sayName;
  sss(); 
  // this.name = ??? 
  // 解析:改成call调用的形式,sss.call(undefined)没有传参,this就是window,因此window.name = 外面的name
  a.sayName(); 
  //this.name = ???
  // 解析:a.sayName.call(a),this就是a,this = 里面的name
  (a.sayName)(); 
  //this.name = ???
  // 解析:js中加不加括号效果都一样,因此相当于(a.sayName).call(a),this=a,可得this=里面的name
  (b = a.sayName)(); 
  //this.name = ???
  //解析:括号和上面的效果一样,也就是此题可以转换成b.call(undefined),那么this=window,window.name=外面的name
}
sayName();
// 解析:sayName.call(),this就是window,window.name = 外面的name

第二题

var length = 10; 
    function fn(){ 
    console.log(this.length)
} 

var obj = { 
    length: 5, 
    method: function(fn){ 
    fn() arguments[0](); 
        } 
}; 

obj.method(fn, 1)

// 提问:输出结果是什么?

使用代码转换方法解析题目如下:

var length = 10; 
    function fn(){ 
    console.log(this.length)
} 

var obj = { 
    length: 5, 
    method: function(fn){ 
        fn() //fn.call(undefined),this = window,window.length = 10 
        arguments[0](); //arguments.0.call(arguments),this=arguments,也就是[fn, 1],两个数的数组,结果为2 
            } 
        }; 
        
obj.method(fn, 1) 
// obj.method.call(obj, fn, 1),this = obj 
// 结合window.length和[fn, 1],可得本次输出结果为10、2