JavaScript的this

在一些编程语言(如 Java、C#)中, this 可以在类中代表当前实例。而到了 JavaScript 这里,this 不只在类中出现,所代表的东西也变化多端,很难理解。本文将简单地梳理一下 JavaScript 中的 this

概括地说,JavaScript 中 this 的值要看它出现在哪种函数中:

  • 普通函数function f() { ... }):this 的值取决于函数的调用方式
  • 箭头函数() => { ... }):this 的值取决于函数的定义位置

下面来看一些具体的例子。

为了更好地贴合现代前端,本文所有的示例均为严格模式。另外,本文所有的示例均在 Node.js CommonJS 环境中运行。

普通函数

情况 1:独立函数

1
2
3
4
5
6
7
"use strict";

function f() {
console.log(this);
}

f(); // undefined

在独立的普通函数中,thisundefined(非严格模式下为 globalThis)。

情况 2:对象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"use strict";

const obj = {
name: "Tom",

f: function () {
console.log(this?.name);
},

g() {
console.log(this?.name);
},
};

obj.f(); // Tom
obj.g(); // Tom

const detached_f = obj.f;
const detached_g = obj.g;

detached_f(); // undefined
detached_g(); // undefined

普通函数作为对象方法时,无论是什么写法,调用方式决定了 this 的值。上述代码中,obj.f === detached_f,然而 obj.f()this 指向对象 obj 本身,detached_f()this 却是 undefined

情况 3:类方法

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
31
32
33
"use strict";

class Person {
constructor() {
this.name = "Tom";

this.h = function () {
console.log(this?.name);
};
}

f() {
console.log(this?.name);
}

g = function () {
console.log(this?.name);
};
}

const p = new Person();

p.f(); // Tom
p.g(); // Tom
p.h(); // Tom

const detached_f = p.f;
const detached_g = p.g;
const detached_h = p.h;

detached_f(); // undefined
detached_g(); // undefined
detached_h(); // undefined

普通函数作为类方法时,和对象方法一样,无论是原型方法还是类实例上的函数,无论是什么写法,调用方式决定了 this 的值。p.f 中的 this 指向当前实例,而 detached_f 中的 thisundefined

情况 4:作为构造器

1
2
3
4
5
6
7
8
"use strict";

function Person() {
this.name = "Tom";
console.log(this); // Person { name: 'Tom' }
}

new Person();

把一个普通函数当作构造器使用时,this 指向一个新的对象。

使用了简写的对象方法和类方法不能当作构造器使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
name: "Tom",

f() {},
};

class Person {
g() {}
}

const p = new Person();

new obj.f(); // TypeError: obj.f is not a constructor
new p.g(); // TypeError: p.g is not a constructor

箭头函数

情况 5:独立函数

1
2
3
4
5
6
7
"use strict";

const f = () => {
console.log(this === module.exports);
};

f(); // true

箭头函数没有自己的 this 绑定,而是利用闭包机制,保留了定义时外部词法环境的 this。这里是顶层的 this,在 Node.js CommonJS 中,也就是 module.exports

情况 6:对象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict";

const obj = {
name: "Tom",

f: () => {
console.log(this === module.exports);
},
};

obj.f(); // true

const detached_f = obj.f;

detached_f(); // true

由于箭头函数中的 this 来自定义时的外部词法环境,而对象亦没有自己的 this 绑定,所以 obj.f 中的 this 直接来自顶层,也就是 module.exports。而且,由于箭头函数并不像普通函数那样——由调用方式决定 this 的值——detached_f 中的 this 也是 module.exports

情况 7:类方法

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
"use strict";

class Person {
constructor() {
this.name = "Tom";

this.g = () => {
console.log(this.name);
};
}

f = () => {
console.log(this.name);
};
}

const p = new Person();

p.f(); // Tom
p.g(); // Tom

const detached_f = p.f;
const detached_g = p.g;

detached_f(); // Tom
detached_g(); // Tom

类构造器中的 this 指向的是当前实例,这点 JavaScript 和 Java/C# 是一样的。因此,p.g 中的 this 是当前实例。而 p.fp.g 基本是等效的,都是依附在每个实例上、而非原型上的。它等效于被定义在构造器中,因此它内部的 this 也是当前实例。更有意思的是,由于箭头函数中的 this 不取决于调用方式,而是依靠闭包机制,detached_fdetached_g 中的 this 也不会丢失。

情况 8:作为构造器

1
2
3
4
5
6
7
8
"use strict";

const Person = () => {
this.name = "Tom";
console.log(this);
};

new Person(); // TypeError: Person is not a constructor

箭头函数和使用了简写的对象方法和类方法一样,不能当作构造器使用。

手动挂载函数

正如前面我们可以从对象/类实例中取下函数那样,我们也可以将函数挂到对象/类实例上。

情况 9:手动挂载的普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"use strict";

function sayName() {
console.log(this.name);
}

const obj = {
name: "Tom",
};

obj.sayName = sayName;
obj.sayName(); // Tom

class Person {
constructor() {
this.name = "Tom";
}
}

const p = new Person();

p.sayName = sayName;
p.sayName(); // Tom

由于普通函数中的 this 取决于调用方式,当 sayName 被挂载到 objp 上、并以 obj.sayName()p.sayName() 的形式被调用时,其中的 this 就会变成 objp

情况 10:手动挂载的箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"use strict";

const sayName = () => {
console.log(this === module.exports);
};

const obj = {
name: "Tom",
};

obj.sayName = sayName;
obj.sayName(); // true

class Person {
constructor() {
this.name = "Tom";
}
}

const p = new Person();

p.sayName = sayName;
p.sayName(); // true

由于箭头函数中的 this 来自它定义时的外部词法环境,挂载并不能影响它其中的 this

call/apply/bind

对于普通函数,可以利用 call/apply/bind 手动指定其中的 this

情况 11:普通函数和 call/apply/bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict";

function sayName(prefix) {
console.log(prefix + this.name);
}

const obj = {
name: "Tom",
};

sayName.call(obj, "Hi, "); // Hi, Tom
sayName.apply(obj, ["Hi, "]); // Hi, Tom

const boundSayName = sayName.bind(obj, "Hi, ");
boundSayName(); // Hi, Tom

对于普通函数,call 会指定 this,接收一个或多个参数,并执行函数。applycall 类似,只是以数组的形式接收参数。bind 则返回一个被预置了 this 和参数的新函数。

这里的对象换成类实例也是一样的,因此不再赘述。

情况 12:箭头函数和 call/apply/bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict";

const f = (prefix) => {
console.log(this === module.exports, prefix);
};

const obj = {
name: "Tom",
};

f.call(obj, "call"); // true call
f.apply(obj, ["apply"]); // true apply

const bound_f = f.bind(obj, "bind");
bound_f(); // true bind

箭头函数也可以使用 call/apply/bind。不过由于它的 this 来源于定义时的外部词法环境,这些方法都不能改变它的 thisbind 也只能预置参数。

这里的对象换成类实例也是一样的,因此不再赘述。

附:手动实现 call/apply/bind

曾经在一次面试中遇到过让你自己实现一遍 call/apply/bind 的题目(当然不能用已有的 call/apply/bind)。虽然这种题目非常八股文,但若熟悉了以上的知识,其实不难拼凑出一个答案。

1
2
3
4
5
6
7
8
9
10
Function.prototype.myCall = function (context, ...args) {
context = context ?? globalThis;
const key = Symbol("fn");

context[key] = this;
const result = context[key](...args);
delete context[key];

return result;
};

首先要实现的是 myCall。在 Function 的原型中加入一个函数 myCall,这里的 thisFunction 类的实例,也就是目标函数(类似于上面的类方法)。而通过将目标函数挂载到 context 上,目标函数中的 this 就变成了 context。最后以 context[key]() 的形式调用,并确保参数和返回值的传递即可。

这里的 Symbol("fn") 是为了生成一个唯一的键,防止覆盖 context 中已有的键值。

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myApply = function (context, args = []) {
return this.myCall(context, ...args);
};

Function.prototype.myBind = function (context, ...boundArgs) {
const fn = this;

return function (...args) {
return fn.myCall(context, ...boundArgs, ...args);
};
};

接着,利用 myCall 和闭包就能实现 myApplymyBind当然,这里的 myCall/myApply/myBind 只是教学用途的简化版,一些细节和真实的 call/apply/bind 是不一样的。

参考资料


JavaScript的this
https://tomzhu.site/2026/07/01/JavaScript的this/
作者
Tom Zhu
发布于
2026年7月1日
许可协议