JavaScript 中 this 的指向必定是大多数前端开发者最头疼的问题之一,就算是经验丰富的新手都可能被绕到迷失方向。本文想做的就是去追寻 this 的迷踪。

this 是什么

this 是 JavaScript 中的一个关键字,它是由运行环境内置的一个变量,且运行期不能从新赋值,那 this 到底是什么?

JavaScript 是基于原型的脚本语言,最后的设计是为了给 web 页面增加一些辅助解决逻辑,比方表单校验、动画成果、时钟等等。而且,晚期 JavaScript 逻辑非常简单,根本都是脚本片段,写起来也是 c 语言 面向过程的滋味,基本没有当初的代码分层、模块加载、打包公布等等高级性能。

再说说配角 this,它还有段陈年往事:作者 Brendan Eich 自己的想法是设计一门相似 Schema 的函数式的脚本语言,然而过后的网景公司决定要蹭风行语言 Java 的热度,所以取名 JavaScript。基于这些思考,作者借鉴了 c 语言 的根本语法和 self 语言 原型式面向对象机制, 塑造了 JavaScript 简略轻量又灵便的特点,然而为了更像 Java 一点,最初硬生生退出了 this, new 的个性。

this 指向的各种场景

因为上述历史起因,导致 JavaScript 的 this 没有前后对立的设计,很难了解就有余奇怪了。它不是 Java 那样指向对象实例,也不是执行上下文,它在不同的代码场景,判断规定不一样。这就是它的复杂性所在。

不过,也不是齐全没有规定可循,总结下来,场景也就那么几种,你把握了判断技巧,就能精确判断 this

1. 全局

全局就是不在任何函数外面,这种状况不论是否严格模式,this 都是指向全局对象,浏览器外面就是 window (前面的示例代码没有特地注明的都浏览器环境)。

console.log(this); // window

2. 一般函数

一般函数的间接调用,函数内 this 非严格模式指向全局对象,严格模式下指向 undefined

function foo() {    console.log(this); // window}foo();function bar() {    'use strict';    console.log(this); // undefined}bar();

另外,函数表达式和立刻执行函数也是这个规定。

const foo = function () {    console.log(this); // window};foo();(function bar() {    console.log(this); // window})();

3. 属性办法

属性办法也是一个函数,不过它是一个对象的属性,调用时通过 . 操作符拜访和调用,办法内 this 指向办法所属对象。 留神:类和原型的办法也是属性办法。

const foo = {    bar: function () {        console.log(this); // {bar}    },};foo.bar();

另外,通过原型和 class 定义方法也是指向实例对象。

function Foo() {    console.log(this); // Foo{}}Foo.prototype.bar = function () {    console.log(this); // Foo{}}const f1 = new Foo();f1.bar();class Foobar() {    function bar () {        console.log(this); // Foobar{}    }}const f2 = new Foobar();f2.bar();

留神,上面场景 baz 属于一般函数调用,因为它不是通过对象属性来拜访的。

const foo = {    bar: function () {        console.log(this); // {bar}                function baz() {            console.log(this); // window        }        baz();    },};foo.bar();

还有这种状况,对象属性弹出后也是一般函数调用,它也不是通过对象属性来拜访的。

const foo = {    bar: function () {        console.log(this); // window    },};const baz = foo.bar;baz();

4. new 构造函数

JavaScript 外面,一般函数都能够作为构造函数,而后应用 new 关键字结构出对象,构造函数内 this 指向新构建的对象。

function Foo() {    console.log(this); // Foo{}}const f1 = new Foo();

5. call, apply

通过 call, apply 间接调用函数(call, apply 性能统一,只是传参格局不一样), this 指向是通过第一个参数指定,须要留神的是:非严格模式下,非对象会尝试转成对象,nullundefined 会转为全局对象;严格模式下,则传的是什么就是什么。

function foo() {    console.log(this); // window}foo.call(null);function bar() {    'use strict';    console.log(this); // null}bar.call(null);

另外留神,对属性办法 call,this 也和一般函数的 call 一样,指向指定参数。

const foo = {    bar: function () {        console.log(this); // {value: 'baz'}    }};foo.bar.call({value: 'baz'});

6. bind

bind 会创立一个绑定了指定 this 的新函数,留神:只会失效一次。

function foo() {    console.log(this); // { value: 'bar' }}const bar = foo.bind({ value: 'bar' });bar();

另外留神,对属性办法 bind,this 也和一般函数的 bind 一样,指向指定参数。而且,bind 之后再 call,不能批改 this 指向。

const foo = {    bar: function () {        console.log(this); // {value: 'baz'}    }};const baz = foo.baz.bind({value: 'baz'});baz.call({value: 'bazzz'});
bind 生成的新函数,尽量不要用作构造函数,个别也只有面试题会这么干,这样做的确不会报错,this 会指向新结构的对象。

7. 箭头函数

箭头函数是 ES6 新增的一种函数,不论是独自调用,还是作为成员办法调用,this 都是指向定义时所在一般函数的 this ,全局定义时指向全局对象。留神:call, apply, bind 对它有效,它也不能用作构造函数。

function foo() {    console.log(this); // { value: 'bar' }        setTimeout(() => {        console.log(this); // { value: 'bar' }    });}foo.call({ value: 'bar' });

留神,有几种特地的状况:

  • 箭头函数作为属性办法,这种状况还是属于全局定义,this 指向全局对象。
const foo = {    bar: () => {        console.log(this); // window    },};foo.bar();
  • 箭头函数作为原型办法,这种状况还是属于全局定义,this 指向全局对象。
function Foo() {    console.log(this); // Foo{}}Foo.prototype.bar = () => {    console.log(this); // window}const f1 = new Foo();f1.bar();
  • 箭头函数作为类属性,这种状况 this 指向对象实例,和下面的原型式定义相比时,可能会让人蛊惑。其实这种状况相当于在构造函数内的定义:this.bar = ...
class Foo {    bar = () => {        console.log(this); // Foo{}    }}const f1 = new Foo();f1.bar();
特地补充一下,场景 3 有个示例是在 class 中定义方法,应用的一般函数,这两种申明形式其实有着本质区别,一般函数申明是定义在原型中的,而箭头函数申明是定义在示例对象中的。

8. 其余补充

除此之外,还有一些非凡场景,this 指向全局对象,冴羽 大佬是依据 ECMAScript 标准去解读的(见参考文章),我本人总结一下:进行了求值计算的就和属性弹出一样,属于一般函数调用,其实还是判断是否是函数调用还是办法调用。

const foo = {    bar: function () {        console.log(this); // window    }};(foo.bar = foo.bar)();(false || foo.bar)();(foo.bar, foo.bar)();

总结

总结几个要点:

  • 判断是一般函数还是属性办法调用;
  • 箭头函数只看定义所在函数;
  • 构造函数指向结构对象;
  • call, apply, bind 看指定参数;
  • 优先级,bind 之后 new,new 优先,bind 之后 call,bind 优先。

参考文章

this - JavaScript | MDN

Gentle Explanation of "this" in JavaScript

JavaScript深刻之从ECMAScript标准解读this · Issue #7 · mqyqingfeng/Blog