JavaScript 中 this 指向所有场景详细分析(译)

2022-09-23 13:14:37 浏览数 (1)

前端开发中,每天和我们见面最多的也许就是 this 关键字了,一直想总结一篇 this 的文章,然后看到了 Gentle Explanation of "this" in JavaScript ,总结的太好了,这里就翻译一下吧。

https://dmitripavlutin.com/gentle-explanation-of-this-in-javascript/


神秘的this

this 关键词已经折磨我很久很久了。

对于 JavaPHP 这类标准语言this 表示当前类的实例化对象,它不能够在类的方法外使用,简单直接,因此并不会让人产生困惑。

但在 JavaScript 中情况就比较复杂了:this 指向当前函数调用的执行上下文(context),有四种函数调用类型:

  • 函数直接调用(function invocationalert('Hello World!')
  • 方法调用(method invocationconsole.log('Hello World!')
  • 构造函数调用(constructor invocationnew RegExp('d')
  • 间接调用(indirect invocationalert.call(undefined, 'Hello World!')

每种调用方式都产生了各自不同的上下文,因此 this 有时候可能并不是我们所期待的。

img

此外,严格模式(strict mode)也会影响到 this 的指向。

弄清 this 指向的关键就是对上边函数调用类型有一个清楚的判断

下边会详细介绍每一种调用类型的区别,以及它们是怎么影响 this 指向的,同时会举例说明一些容易判断出错的陷阱。

1. 函数直接调用(function invocation)

定义:一个可以解析成函数对象的表达式,紧跟着左括号,然后是逗号分割的参数,最后是右括号。例如 parseInt('18')

下边看一个简单的函数直接调用的例子:

代码语言:javascript复制
function hello(name) {
  return 'Hello '   name   '!';
}
// Function invocation
const message = hello('World');

hello('World') 就是一个函数直接调用:hello可以解析成一个函数对象,紧跟着是用括号括起来的 World 参数。

函数直接调用和通过对象属性 obj.myFunc() 是不同的,它叫做方法调用 method invocation(后边介绍的第 2 种类型)。比如 [1,5].join(',') 这种不是一个函数直接调用,而是方法调用。这两种是最容易混淆的,需要特别注意

此外,一个 IIFE(immediately-invoked function expression) 也属于函数直接调用。

代码语言:javascript复制
// IIFE
const message = (function(name) {
  return 'Hello '   name   '!';
})('World');

第一对括号内的 (function(name) {...}) 是一个可以解析成函数对象的表达式,接下来的一对括号中 ('World') 括的就是参数列表。

1.1 函数直接调用中的 `this` 指向

首先需要知道全局对象是什么,不同执行环境中全局对象不同。在浏览器中全局对象是 windowNode.js 中的全局对象是 global。下边主要以浏览器的执行环境来分析。

img

在「函数直接调用」中,执行上下文就是全局对象,也就是 this 的指向。

让我们看下边的例子:

代码语言:javascript复制
function sum(a, b) {
  console.log(this === window); // => true
  this.myNumber = 20; // add 'myNumber' property to global object
  return a   b;
}
// sum() is invoked as a function
// this in sum() is a global object (window)
sum(15, 16);     // => 31
window.myNumber; // => 20

当调用 sum(15, 16) 的时候,JavaScript 会自动将 this 指向全局对象(浏览器中的 window)。

this 在函数外的顶级作用域中使用的时候,同样也是指向全局对象的:

代码语言:javascript复制
console.log(this === window); // => true
this.myString = 'Hello World!';
console.log(window.myString); // => 'Hello World!'

html 中的 script 中也是一样的。

代码语言:javascript复制
<!-- In an html file -->
<script type="text/javascript">
 console.log(this === window); // => true
</script>

1.2 函数直接调用中的 `this` 指向(严格模式)

严格模式下,函数直接调用中的 thisundefined

严格模式在ECMAScript 5.1 出现,对 js 一些语法进行了限制,可以提供一个更加安全、健壮的错误检查机制。

在函数体顶部加入 'user strict' 即可使用严格模式。

严格模式会影响当前上下文环境(context),在函数直接调用场景中,this 会变为 undefind,而不是上边介绍的全局对象。

img

下边举一个使用严格模式的例子:

代码语言:javascript复制
function multiply(a, b) {
  'use strict'; // enable the strict mode
  console.log(this === undefined); // => true
  return a * b;
}
// multiply() function invocation with strict mode enabled
// this in multiply() is undefined
multiply(2, 5); // => 10

当使用严格模式调用 multiply(2, 5) 的时候,this 就是 undefined

严格模式不仅对当前作用域有效,它内部的作用域(当前作用域内定义的函数内)也会是严格模式。

代码语言:javascript复制
function execute() {
  'use strict';
  function concat(str1, str2) {
    // the strict mode is enabled too
    console.log(this === undefined); // => true
    return str1   str2;
  }
  console.log(this === undefined); // => true
  // concat() is invoked as a function in strict mode
  // this in concat() is undefined
  concat('Hello', ' World!'); // => "Hello World!"
}
execute();

上边的代码中,use strict 是写在 execute 函数体的头部,函数内部的 this 会是 undefined。同时,因为 concat 是定义在 execute 内部,它也会继承严格模式。因此 concat('Hello', ' World!') 的调用,其内部的 this 也会指向 undefined

一个单独 js 文件可能包含多个作用域,因此不同的作用域可能分别处于非严格模式和严格模式。

代码语言:javascript复制
function nonStrictSum(a, b) {
  // non-strict mode
  console.log(this === window); // => true
  return a   b;
}
function strictSum(a, b) {
  'use strict';
  // strict mode is enabled
  console.log(this === undefined); // => true
  return a   b;
}
// nonStrictSum() is invoked as a function in non-strict mode
// this in nonStrictSum() is the window object
nonStrictSum(5, 6); // => 11
// strictSum() is invoked as a function in strict mode
// this in strictSum() is undefined
strictSum(8, 12); // => 20

1.3 陷阱:内部函数的 `this` 指向

⚠️一个很普遍的陷阱:对于函数直接调用,可能会认为如果是函数内部定义的函数,它的 this 会指向外部函数的上下文。

0 人点赞