前端开发中,每天和我们见面最多的也许就是 this
关键字了,一直想总结一篇 this
的文章,然后看到了 Gentle Explanation of "this" in JavaScript ,总结的太好了,这里就翻译一下吧。
https://dmitripavlutin.com/gentle-explanation-of-this-in-javascript/
神秘的this
this
关键词已经折磨我很久很久了。
对于 Java
、PHP
这类标准语言,this
表示当前类的实例化对象,它不能够在类的方法外使用,简单直接,因此并不会让人产生困惑。
但在 JavaScript
中情况就比较复杂了:this
指向当前函数调用的执行上下文(context
),有四种函数调用类型:
- 函数直接调用(
function invocation
:alert('Hello World!')
- 方法调用(
method invocation
:console.log('Hello World!')
- 构造函数调用(
constructor invocation
:new RegExp('d')
- 间接调用(
indirect invocation
:alert.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)
也属于函数直接调用。
// IIFE
const message = (function(name) {
return 'Hello ' name '!';
})('World');
第一对括号内的 (function(name) {...})
是一个可以解析成函数对象的表达式,接下来的一对括号中 ('World')
括的就是参数列表。
1.1 函数直接调用中的 `this` 指向
首先需要知道全局对象是什么,不同执行环境中全局对象不同。在浏览器中全局对象是 window
,Node.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
在函数外的顶级作用域中使用的时候,同样也是指向全局对象的:
console.log(this === window); // => true
this.myString = 'Hello World!';
console.log(window.myString); // => 'Hello World!'
在 html
中的 script
中也是一样的。
<!-- In an html file -->
<script type="text/javascript">
console.log(this === window); // => true
</script>
1.2 函数直接调用中的 `this` 指向(严格模式)
严格模式下,函数直接调用中的
this
是undefined
。
严格模式在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
文件可能包含多个作用域,因此不同的作用域可能分别处于非严格模式和严格模式。
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
会指向外部函数的上下文。