让 JS this 指向不再迷惑你

957 阅读3分钟

前言

相信你一定被 javaScript 中 this 指向迷惑过。为什么这里 this 是这样,为什么这里的 this 是那样?

小小的脑袋,大大的疑问。

指向运行时判定

javaScript 一开始并不知道当前的 this 指向,必须在运行时才能得知当前的 this 指向。

所以也就有了这样一句话,谁调用就指向谁

这样一句话并不能我们对 this 指向问题有一个深刻的认识,那我们进行举例一个个认识。

html 篇

直接打印

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button type="button" onclick="console.log(this)">查看 this</button>
  </body>
</html>

这时候打印的是 button 元素。onclick 事件由 button DOM 元素进行调用。

借助函数打印

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button type="button" onclick="sayThis()">查看 this</button>
  </body>
  <script>
    function sayThis(){
      console.log(this)
    }
  </script>
</html>

这时候打印的是 window 对象。

注意:这里的 onclick 调用的是 sayThis(),JS 应该是在外层包了一层 function。故这里真正执行的是 window.sayThis()。如果不能理解,可见下方例子:

let temp = {
  test:function(){
    test()
  }
}
function test(){
  console.log(this)
}
temp.test()

temp 里的 test 函数执行是通过当前的 global 环境进行执行的(为什么不说 window,因为这里还考虑到了 node.js 平台)。

利用 DOM 操作去打印

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button type="button">查看 this</button>
  </body>
  <script>
    var button = document.getElementsByTagName('button')[0]
    button.onclick = sayThis
    function sayThis(){
      console.log(this)
    }
  </script>
</html>

这时候打印的是 button 元素。

这个不难理解,由 button DOM 元素进行调用。

构造函数篇

let name = '张三'
function Person(name) {
  this.name = name
}
let people = new Person('李四')
console.log(people.name)

这里打印的为李四。

对于构造函数来说,this 将被绑定到被实例化的对象上。

箭头函数篇

箭头函数并不具有 this,而是根据定义的环境自动绑定上 this。

let a = 0
let temp = {
  a: 1,
  b: 2,
  test() {
    console.log(this.a)
  },
}
temp.test()

这里打印的为 1,因为由 temp 调用的 test 函数。

let a = 0
let temp = {
  a: 1,
  b: 2,
  test: () => {
    console.log(this.a)
  },
}
temp.test()

这里打印的为 0(注意这里只是 web 端,Node 端为 undefined),因为箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。

函数回调篇

在对一些原生方法传递函数时,this 指向并不会像我们期望那样发生变化。例如:setTimeout

var name='window'
function hello(){
  setTimeout(function(){
    console.log(this.name)
  }, 100);
}
let obj={
  name:'obj',
  hello
}
// widow
obj.hello()

setTimeout等传递函数方法是一种特殊情况,setTimeout中的this都指向window(同样这里只是适用于 web 端)。

同理数组里的 map、filter等也是如此。

Node 篇

直接打印 this

// {}
console.log(this)

在 Node 中直接打印 this,我们只会得到一个空对象

map、filter等函数回调

let arr = [1]
arr.filter(function(item){
  // global 对象
  console.log(this)
})

这里比较特殊返回的是 global 对象

setTimeout、setInterval

setTimeout(function(){
  // Timeout 对象
  console.log(this)
})

这里更加特殊返回的是 Timeout 对象。

总结

在 web 端中 this 指向,其实我们只需奉行:

  1. 谁调用 this 指向谁
  2. 注意箭头函数的上下文绑定
  3. 注意 JS 方法里的回调函数的 this

而在 node 端我们需注意更多:

  1. 谁调用 this 指向谁
  2. 注意箭头函数的上下文绑定
  3. 注意 this 的直接打印
  4. 注意 JS 方法里的回调函数的 this(不包括setTimeout、setInterval)
  5. 注意 setTimeout、setInterval