JavaScript之this关键字

1. 背景介绍

在开发过程中,常常被javascript中的this搞晕,直到自己静下心来系统的把他梳理清楚,下面是由浅如深的理解this关键字.

2. javascript中的this简介

面向对象语言中 this 表示当前对象的一个引用.
但在 JavaScript 中 this在编译时不是固定不变的,它会随着运行时环境的改变而改变.

  • 在方法中,this 表示该方法所属的对象。
  • 如果单独使用,this 表示全局对象。
  • 在函数中,this 表示全局对象。
  • 在函数中,在严格模式下,this 是未定义的(undefined)。
  • 在事件中,this 表示接收事件的元素。
  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象

3. 普通方法中的this

  • 首先我们来定义一个普通js方法,在方法中我们打印this以及this相关的属性.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var a = 9999;
function explainThis() {
//在函数内部访问全局变量
console.log(a); //output: 9999
console.log(this.a); //output: 9999
console.log(window.a); //output: 9999
console.log(this); //output: window对象
console.log(window.a === this.a); //output: true
console.log(window.a === a); //output: true
console.log(window === this); //output: true
}
explainThis();
</script>
</body>
</html>
  • 说明
    • 在这个例子中我们在函数外部定义了一个变量,我们尝试在函数内部访问这个变量,能访问到该变量及其值,这很好理解,因为很多语言中都有全局变量,此时a相当于一个全局变量.

    • 然后我们通过this关键字来访问a变量, 也能访问到a, 这里就有些疑惑了,这里的this在语义上到底指什么,this翻译为中文为"这个,这里",这个是哪个? 这里是哪里?而且this在其他编程语言中是指方法所属对象,我们此处还还没有涉及到对象的概念,当前还是是面向过程编程,但是现在居然有this可以用了.什么乱七八糟的,我们知道有这么回事就好了,到后面我们会继续探究这里的this.

    • 紧接着我们通过javascript内置的全局变量window去访问全局变量a, 这个好理解,全局变量嘛,把它挂在window下面或gloabal.

    • 紧接着我们打印一下this看看他到底是什么样的一个结构,一打印发现这里的this是就是window对象.但是这里的this为什么是window从逻辑上仍然不好理解,知其然不知其所以然.既然能使用window去访问全局对象,为何还要有个this, 而且this在此的意义令人费解,而且看不出有何存在的必要.当然设计这门语言的人这样设计了我们只能试图去理解它,至于很多面试题特意去考这个知识点,似乎就有点孔乙己卖弄茴香豆几种写法的嫌疑了,装高深.后面会继续探讨这里的this.

    • 接下来我们来探讨一下这里的window有没有存在的必要.其实在其他语言C, pascal, python中都有全局变量,这些全局变量可以直接通过全局变量的变量名去访问,而不需要通过global, window等内置变量去访问,就访问用户自定义的变量而言window也没有存在的必要.但是考虑到浏览器为我们提供了很多的api, 这些系统自带的变量和api接口,通过window内置变量作为统一入口去访问,还是很有必要的,例如:

      1
      2
      3
      4
      var version = window.navigator.appVersion;
      window.addEventListener("click", function (e) {
      //do something
      });

4. 对象构造方法中的this

  • 接下来我们, 我们将上面的例子稍微改变一下,这时结果就会发生变化.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var a = 9999;
function explainThis() {
console.log(a); // 9999
console.log(this.a); //output: undefined
console.log(window.a); // 9999
console.log(this); // object
console.log(window.a === this.a); //output: false
console.log(this.a); //output: undefined
console.log(window.a === a); //output: true
console.log(window === this); //output: false
}
new explainThis(); // 改变就在这里, 使用new关键字去创建一个对象.
</script>
</body>
</html>
  • 同样的函数,因为调用方式不一样,产生的结果完全不一样,这对于一个喜欢函数编程,崇尚幂等性的人来说这几乎是完全不能接受的语言特性,而且javascript是支持函数式编程的,到了这里完全是不伦不类.当然入乡只能随俗,入了这门语言的门,就只能试图去理解它,虽然很丑陋,还是要去接受,这就叫做无奈.
  • 这里如果按照面向对象的思想似乎变得好理解一些,this就是对象本身.但是其中又耦合了全局变量 window, a 也变得不那么纯粹,也没有包的概念有效的避免重名.回到这门语言诞生的时代,还是可以接受的,那时新生代语言毕竟受c, c++影响较大,全局变量多不做隔离的.
  • 所以这里this就是对象本身,那么再回顾上一节的this为什么会存在并指向了window对象呢? 如果要深究,就得研究javascript的实现了,javascript的this实际上是一个内置对象,而且全局就这么一个this对象,它相当一个指针或者reference,当它执行对象的方法时就会将this指向对象的本身,在上一节中,explainThis相当于全局函数,所以它将它当作全局方法来使用时他是属于全局对象global或windows的方法,此时this指向window. 如果当作对象构造函数时,一旦新对象创建,this就会指向新的对象,所以在本例子它指向了新对象.这可能就是作者当时的思路.但是站在一个面向对象编程爱好者和函数编程爱好者的角度,是很难理解的.作者当时没有使用单独的关键字去区分类和对象的定义,全局方法的定义,这种不成熟的思路越来越成为一种技术债务,后面的es6+也好,typescript也好,coffeescript也好都在慢慢偿还这些债务.

5. 事件方法中的this

  • 如果this只有windows和对象本身两种变化,也就不值得写一篇几百字的博文单独介绍它了,对于一个关键字需要写长篇大论来介绍它.并不是因为它有多高深,恰恰相反,而是说明了它设计得很丑陋.恰恰那些不用介绍一看就明白,一用就对的关键字,方法,函数,类,才是真正优秀的.

  • 下面来看this的第三种变化,事件方法中的this. 为了说明这一点,我设计了一个新的例子.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <script>
    var a = 1;
    function explainThis() {
    console.log(this.a); //这里会打印什么呢?
    }

    explainThis.prototype = {
    a: 2
    }

    x = {
    a:3,
    explainThis: explainThis
    };

    explainThis(); //output: 1
    new explainThis(); //output: 2
    x.explainThis(); //output: 3

    </script>
    </body>
    </html>
  • 同样的一行代码console.log(this.a); 没有传任何参数的情况下,它可以有三种不同的输出.

  • 甚至可能由于所属对象的结构不同而报错.

  • 上面的例子很好的说明了this关键所代表的内容,只能在运行时才能确定,即使将this定义在某个对象执行,也不能保证.完全由运行时来决定.this的变动会让设计js类库的人非常头疼,比较好奇是不是很多人IT人的头发是被this消耗光的.

6. 匿名函数中的this

7. nodejs中的this

8. 参考文档

‘this’ in TypeScript