本文共 1065 字,读完只需 4 分钟

概述

在 JAVA 中,this 的概念很透彻,就是指向当前对象(方法和属性的持有者),在编译的时候就能确定 this 指代,而由于 JavaScript 中 this 是动态绑定,或称为运行期绑定的,在绝大多数情况下,函数的调用方式决定了 this 的值,所以在 JS 中不能在定义时决定地定义 this 是哪个上下文对象。

this 的指向只和函数的调用位置(方式)有关,和函数声明的位置无关。

本文就从 this 应用场景调用方式的角度,解析 this 的用法。

首先,要知道,this 的作用是什么?

函数体内部,指代函数当前的运行上下文。

一、全局上下文

在全局上下文中,this 指向全局对象,在浏览器中指向 window,在 NodeJs 中指向 global。

1
2
3
4
var a = "全局";
console.log(this); // 浏览器:window
console.log(this); // nodejs:global
console.log(this.a); // "全局"

这是 JS 中的默认绑定,在全局上下文中,this 会默认绑定到 全局对象。

二、函数声明

在非严格模式下,未加关键字 var 声明的变量,会成为全局对象下的属性,所以,此时
在严格模式下,this 将保持他进入执行上下文时的值,所以下面的 this 将会默认为 undefined。

1
2
3
4
5
6
function foo() {
"use strict";
console.log(this);
}

foo(); // undefined 严格模式

上述也是 JS 中 this 的默认绑定,在函数定义中,函数中的 this 会默认绑定到全局对象或者 undefined。

当函数作为对象里的方法被调用时,函数中 this 是调用该函数的对象。

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log("foo");
}

var obj = {
this.bar = "bar";
this.foo = foo;
}

obj.foo(); // "foo"

在调用位置,是 obj 对象调用了 foo 函数,此时 this 就指向了 obj 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
console.log(this.bar);
}

var obj2 = {
bar: "2",
foo: foo
};

var obj1 = {
bar: "1",
obj2: obj2
};

obj1.obj2.foo(); // "2";

这是 JS 中 this 的隐式绑定,this 会隐式绑定到调用的最近一层上下文对象。

三、call & apply

其实在函数中,函数正确的调用应该是用 call, apply 函数的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
console.log(this.bar);
}

let obj = {
bar: "1",
foo: foo
};

obj.foo(); // `.` 点调用其实是语法糖

foo.call(obj); // 1 正确的姿势
foo.apply(obj); // 1

这是 JS 中 this 的显式绑定,函数通过从 call 和 apply 函数可以显式指定函数的调用对象。

ES5 提供了一个 bind()方法:

1
2
3
4
5
6
7
8
9
10
let obj2 = {
a: 2
};

let foo = function() {
console.log(this.a);
};

let bar = foo.bind(obj2);
bar(); // 2;

就是 JS 中 this 的硬绑定,bind() 函数会返回一个指定了调用对象的函数,返回的函数的被指定了 this 且 this 无法改变。

四、构造函数

当函数加上关键字 new 后,函数会变成一个构造函数,此时构造函数中的 this 指向即将创建的对象实例,同时对象实例会关联到构造函数的原型对象 prototype 上。

1
2
3
4
5
6
7
function Foo() {
this.a = 3;
}

var obj = new Foo();

obj.a; // 3

五、箭头函数

ES6 中,提供了函数定义的语法糖,让定义函数变得更简洁(没有 arguments, 没有原型)。同时上面四条绑定规则在箭头函数中不适用,使得 this 的查找更可控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一般函数:
var a = 1;
var obj = {
a: 2,
say: function() {
console.log(this.a);
}
};
obj.say(); // console.log 打印值为 2

// 箭头函数
var a = 1;
var obj = {
a: 2,
say: () => {
console.log(this.a);
}
};
obj.say(); // 1 !!!

由于箭头函数定义时, obj {} 不是执行上下文,say 变量引用的箭头函数,其 this 是父级执行上下文,也就是全局上下文,所以 this.a 就是 全局变量 a: 1;

在箭头函数中,this 与封闭词法上下文的 this 保持一致,this 被永久绑定到了它最近一层非箭头函数的 this,而与函数的调用位置无关。

总结

在绝大多数情况下,函数的调用方式决定了 this 的值,但最终都指向了调用了它的那个上下文对象。