this
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
与其他语言相比,函数的 this
关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
在绝大多数情况下,函数的调用方式决定了 this
的值(运行时绑定)。this
不能在执行期间被赋值,并且在每次函数被调用时 this
的值也可能会不同。ES5 引入了 bind 方法来设置函数的 this
值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this
的值将保持为闭合词法上下文的值)。
尝试一下
语法
this
值
在非严格模式下,this
总是指向一个对象,在严格模式下可以是任意值。有关如何确定该值的更多信息,请参阅下面的描述。
描述
this
的值取决于它出现的上下文:函数、类或全局。
函数上下文
在函数内部,this
的值取决于函数如何被调用。可以将 this
看作是函数的一个隐藏参数(就像函数定义中声明的参数一样),this
是语言在函数体被执行时为你创建的绑定。
对于典型的函数,this
的值是函数被访问的对象。换句话说,如果函数调用的形式是 obj.f()
,那么 this
就指向 obj
。例如:
function getThis() {
return this;
}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.getThis = getThis;
obj2.getThis = getThis;
console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }
注意,虽然函数是相同的,但是根据其调用的方式,this
的值是不同的。这与函数参数的工作方式类似。
this
的值不是拥有此函数作为自己属性的对象,而是用于调用此函数的对象。你可以通过调用对象在原型链中的方法来证明这一点。
const obj3 = {
__proto__: obj1,
name: "obj3",
};
console.log(obj3.getThis()); // { name: 'obj3' }
this
的值总是根据调用函数的方式而改变,即使函数是在创建对象时定义的:
const obj4 = {
name: "obj4",
getThis() {
return this;
},
};
const obj5 = { name: "obj5" };
obj5.getThis = obj4.getThis;
console.log(obj5.getThis()); // { name: 'obj5', getThis: [Function: getThis] }
如果方法被访问的值是一个原始值,this
也将是一个原始值——但只有当函数处于严格模式下会如此。
function getThisStrict() {
"use strict"; // 进入严格模式
return this;
}
// 仅用于演示——你不应该改变内置的原型对象
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"
如果函数在没有被任何东西访问的情况下被调用,this
将是 undefined
——但只有在函数处于严格模式下会如此。
console.log(typeof getThisStrict()); // "undefined"
在非严格模式下,一个特殊的过程称为 this
替换确保 this
的值总是一个对象。这意味着:
- 如果一个函数被调用时
this
被设置为undefined
或null
,this
会被替换为globalThis
。 - 如果函数被调用时
this
被设置为一个原始值,this
会被替换为原始值的包装对象。
function getThis() {
return this;
}
// 仅用于演示——你不应该修改内置的原型对象
Number.prototype.getThis = getThis;
console.log(typeof (1).getThis()); // "object"
console.log(getThis() === globalThis); // true
在典型的函数调用中,this
是通过函数的前缀(点之前的部分)隐式传递的,就像一个参数。你也可以使用 Function.prototype.call()
、Function.prototype.apply()
或 Reflect.apply()
方法显式设置 this
的值。使用 Function.prototype.bind()
,你可以创建一个新的函数,无论函数如何被调用,其 this
的值都不会改变。当使用这些方法时,如果函数是在非严格模式下,上述 this
替换规则仍然适用。
回调
当一个函数作为回调函数传递时,this
的值取决于如何调用回调,这由 API 的实现者决定。回调函数通常以 undefined
作为 this
的值被调用(直接调用,而不附加到任何对象上),这意味着如果函数是在非严格模式,this
的值会是全局对象(globalThis
)。这在迭代数组方法、Promise()
构造函数等例子中都是适用的。
function logThis() {
"use strict";
console.log(this);
}
[1, 2, 3].forEach(logThis); // undefined、undefined、undefined
一些 API 允许你为回调函数的调用设置一个 this
值。例如,所有的迭代数组方法和相关的方法,如Set.prototype.forEach()
,都接受一个可选的 thisArg
参数。
[1, 2, 3].forEach(logThis, { name: "obj" });
// { name: 'obj' }, { name: 'obj' }, { name: 'obj' }
偶尔,回调函数会以一个非 undefined
的 this
值被调用。例如,JSON.parse()
的 reviver
参数和 JSON.stringify()
的 replacer
参数都会把 this
设置为正在被解析/序列化的属性所属的对象。
箭头函数
在箭头函数中,this
保留了闭合词法上下文的 this
值。换句话说,当对箭头函数求值时,语言不会创建一个新的 this
绑定。
例如,在全局代码中,无论是否在严格模式下,由于全局上下文绑定,this
值总是 globalThis
。
const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true
箭头函数在其周围的作用域上创建一个 this
值的闭包,这意味着箭头函数的行为就像它们是“自动绑定”的——无论如何调用,this
都绑定到函数创建时的值(在上面的例子中,是全局对象)。在其他函数内部创建的箭头函数也是如此:它们的 this
值保持为闭合词法上下文的 this
。参见下面的例子。
此外,当使用 call()
、bind()
或 apply()
调用箭头函数时,thisArg
参数会被忽略。不过,你仍然可以使用这些方法传递其他参数。
const obj = { name: "obj" };
// 尝试使用 call 设置 this
console.log(foo.call(obj) === globalObject); // true
// 尝试使用 bind 设置 this
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true
构造函数
当一个函数被用作构造函数(使用 new
关键字)时,无论构造函数是在哪个对象上被访问的,其 this
都会被绑定到正在构造的新对象上。除非构造函数返回另一个非原始值,不然 this
的值会成为 new
表达式的值。
function C() {
this.a = 37;
}
let o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // 38
在第二个例子(C2
)中,因为在构造过程中返回了一个对象,this
被绑定的新对象被丢弃。(这基本上使得语句 this.a = 37;
成为了死代码。它并不完全是死代码,因为它被执行了,但是它可以被消除而不产生任何外部效果。)
super
当一个函数以 super.method()
的形式被调用时,method
函数内的 this
与 super.method()
调用周围的 this
值相同,通常不等于 super
所指向的对象。这是因为 super.method
不是像上面的对象成员访问——它是一种特殊的语法,有不同的绑定规则。有关示例,请参见 super
参考。
类上下文
一个类可以被分为两个上下文:静态和实例。构造函数、方法和实例字段初始化器(公有或私有)属于实例上下文。静态方法、静态字段初始化器和静态初始化块属于静态上下文。this
值在每个上下文中都是不同的。
类构造函数总是通过 new
调用,所以它们的行为与构造函数相同:this
值是正在创建的新实例。类方法的行为像对象字面量中的方法——this
值是方法被访问的对象。如果方法没有转移到另一个对象,this
通常是类的一个实例。
静态方法不是 this
的属性。它们是类本身的属性。因此,它们通常在类上访问,this
是类(或子类)的值。静态初始化块也是在 this
设置为当前类的情况下进行求值的。
字段初始化器也在类的上下文中执行。实例字段是在 this
被设置为正在构造的实例的情况下被初始化的。静态字段是在 this
被设置为当前类的情况下被初始化的。这就是为什么字段初始化器中的箭头函数对于实例字段绑定到实例,对于静态字段绑定到类。
class C {
instanceField = this;
static staticField = this;
}
const c = new C();
console.log(c.instanceField === c); // true
console.log(C.staticField === C); // true
派生类构造函数
与基类构造函数不同,派生构造函数没有初始的 this
绑定。调用 super()
在构造函数中创建一个 this
绑定,基本上和求值以下代码的效果类似,其中 Base
是基类:
this = new Base();
警告:
在调用 super()
之前引用 this
将抛出错误。
派生类在调用 super()
之前不能有返回,除非构造函数返回一个对象(这样 this
值就会被覆盖)或者类根本没有构造函数。
class Base {}
class Good extends Base {}
class AlsoGood extends Base {
constructor() {
return { a: 5 };
}
}
class Bad extends Base {
constructor() {}
}
new Good();
new AlsoGood();
new Bad(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
全局上下文
在全局执行上下文中(在任何函数或类之外;可能在全局范围内定义的块或箭头函数内部),this
值取决于脚本运行的执行上下文。像回调一样,this
值由运行时环境(调用者)确定。
在脚本的顶层,无论是否在严格模式下,this
会指向globalThis
。这通常与全局对象相同——例如,如果源代码放在 HTML 的 <script>
元素内并作为脚本执行,this === window
。
备注: globalThis
通常与全局对象的概念相同(即向 globalThis
添加属性会使它们成为全局变量)——这对于浏览器和 Node 是这样的——但主机可以为 globalThis
提供与全局对象无关的不同值。
// 在网页浏览器中,window 对象也是全局对象:
console.log(this === window); // true
this.b = "MDN";
console.log(window.b); // "MDN"
console.log(b); // "MDN"
如果源代码作为模块加载(对于 HTML,这意味着在 <script>
标签中添加 type="module"
),在顶层,this
总是 undefined
。
如果源代码使用 eval()
执行,this
与直接调用 eval 的闭合上下文相同,或者与间接调用 eval 的 globalThis
(就像它在单独的全局脚本中运行一样)相同。
function test() {
// 直接调用 eval
console.log(eval("this") === this);
// 间接调用 eval,非严格模式
console.log(eval?.("this") === globalThis);
// 间接调用 eval,严格模式
console.log(eval?.("'use strict'; this") === globalThis);
}
test.call({ name: "obj" }); // 输出 3 个 "true"
请注意,某些源代码虽然看起来像全局作用域,但在执行时实际上被包装在一个函数中。例如,Node.js CommonJS 模块被包装在一个函数中,并且 this
值设置为 module.exports
。事件处理器属性执行时,this
设置为它们附加到的元素。
对象字面量不创建 this
作用域——只有在对象内定义的函数(方法)才会这样做。在对象字面量中使用 this
会从周围的作用域继承值。
const obj = {
a: this,
};
console.log(obj.a === window); // true
示例
函数上下文中的 this
this
参数的值取决于函数如何被调用,而不是它如何被定义。
// 对象可以作为第一个参数传递给 'call' 或 'apply',
// 并且 'this' 将被绑定到它。
const obj = { a: "Custom" };
// 使用 var 声明的变量成为 'globalThis' 的属性。
var a = "Global";
function whatsThis() {
return this.a; // 'this' 取决于函数如何被调用
}
whatsThis(); // 'Global'; 在非严格模式下,'this' 参数默认为 'globalThis'
obj.whatsThis = whatsThis;
obj.whatsThis(); // 'Custom'; 'this' 参数被绑定到 obj
使用 call()
和 apply()
,你可以对 this
进行传值,就像它是一个显式参数。
function add(c, d) {
return this.a + this.b + c + d;
}
const o = { a: 1, b: 3 };
// 第一个参数被绑定到隐式的 'this' 参数;
// 剩余的参数被绑定到命名参数。
add.call(o, 5, 7); // 16
// 第一个参数被绑定到隐式的 'this' 参数;
// 第二个参数是一个数组,其成员被绑定到命名参数。
add.apply(o, [10, 20]); // 34
this 和对象转换
在非严格模式下,如果一个函数被调用时其 this
值不是一个对象,那么 this
值会被替换为一个对象。null
和 undefined
会变成 globalThis
。像 7
或 'foo'
这样的原始值会使用相关的构造函数转换为对象,所以原始数值 7
会被转换为一个 Number
包装类,字符串 'foo'
会被转换为一个 String
包装类。
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call("foo"); // [object String]
bar.call(undefined); // [object Window]
bind() 方法
调用 f.bind(someObject)
会创建一个新函数,这个新函数具有与 f
相同的函数体和作用域,但 this
的值永久绑定到 bind
的第一个参数,无论函数如何被调用。
function f() {
return this.a;
}
const g = f.bind({ a: "azerty" });
console.log(g()); // azerty
const h = g.bind({ a: "yoo" }); // bind 只能生效一次!
console.log(h()); // azerty
const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); // 37 37 azerty azerty
箭头函数中的 this
箭头函数在闭合执行上下文中创建了 this
值的闭包。在下面的例子中,我们创建了一个对象 obj
,它有一个方法 getThisGetter
,该方法返回一个函数,这个函数返回 this
的值。返回的函数是作为箭头函数的形式创建的,所以它的 this
永久地绑定到其执行上下文中的 this
。getThisGetter
内部的 this
值可以在调用中设置,这反过来又设置了返回函数的返回值。我们假设 getThisGetter
是一个非严格函数,这意味着它包含在一个非严格模式的脚本中,并且没有进一步嵌套在类或严格模式的函数中。
const obj = {
getThisGetter() {
const getter = () => this;
return getter;
},
};
我们可以作为 obj
的方法调用 getThisGetter
,这将在其主体内部将 this
绑定到 obj
。返回的函数被赋值给一个变量 fn
。现在,当调用 fn
时,返回的 this
值仍然是通过调用 getThisGetter
设置的值,即 obj
。如果返回的函数不是箭头函数,那么这样的调用会导致 this
值为 globalThis
,因为 getThisGetter
是非严格模式的。
const fn = obj.getThisGetter();
console.log(fn() === obj); // true
但是,如果你解绑 obj
的方法而不调用它,需要小心,因为 getThisGetter
仍然是一个方法,它有一个可变的 this
值。在下面的例子中,调用 fn2()()
将返回 globalThis
,因为它遵循 fn2()
的 this
,由于它没有附加到任何对象上进行调用,所以是 globalThis
。
const fn2 = obj.getThisGetter;
console.log(fn2()() === globalThis); // 在非严格模式下为 true
这种行为在定义回调时非常有用。通常,每个函数表达式都创建自己的 this
绑定,这会遮蔽上层作用域的 this
值。现在,如果你不关心 this
值,你可以将函数定义为箭头函数,并且只在你需要的地方创建 this
绑定(例如,在类方法中)。参见 setTimeout()
的示例。
getter 或 setter 中的 this
在 getter 和 setter 中,this
是基于访问属性的对象,而不是定义属性的对象。用作 getter 或 setter 的函数会将其 this
绑定到正在设置或获取属性的对象。
function sum() {
return this.a + this.b + this.c;
}
const o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
},
};
Object.defineProperty(o, "sum", {
get: sum,
enumerable: true,
configurable: true,
});
console.log(o.average, o.sum); // 2 6
DOM 事件处理器中的 this
当一个函数被用作事件处理器时,它的 this
参数绑定到放置监听器的 DOM 元素上(一些浏览器对于使用 addEventListener()
以外的方法动态添加的监听器并不遵循这个约定)。
// 当作为监听器调用时,将相关元素变为蓝色
function bluify(e) {
// 总是为 true
console.log(this === e.currentTarget);
// 当 currentTarget 和 target 是同一个对象时为 true
console.log(this === e.target);
this.style.backgroundColor = "#A5D9F3";
}
// 获取文档中的每一个元素
const elements = document.getElementsByTagName("*");
// 添加 bluify 作为点击监听器,所以当元素被点击时,它会变蓝
for (const element of elements) {
element.addEventListener("click", bluify, false);
}
内联事件处理器中的 this
当代码从内联事件处理器属性调用时,它的 this
绑定到放置监听器的 DOM 元素上:
<button onclick="alert(this.tagName.toLowerCase());">Show this</button>
上面的 alert 会显示 button
。注意只有外层代码中的 this
是这样设置的:
<button onclick="alert((function(){return this})());">Show inner this</button>
在这种情况下,内部函数的 this
指向 globalThis
对象(即非严格模式下,调用的函数未设置 this
时指向的默认对象)。
类中的绑定方法
和其他普通函数一样,方法中的 this
值取决于它们如何被调用。有时,改写这个行为,让类中的 this
值总是指向这个类实例会很有用。为了做到这一点,可在构造函数中绑定类方法:
class Car {
constructor() {
// 绑定 sayBye 而不是 sayHi 来展示差异
this.sayBye = this.sayBye.bind(this);
}
sayHi() {
console.log(`Hello from ${this.name}`);
}
sayBye() {
console.log(`Bye from ${this.name}`);
}
get name() {
return "Ferrari";
}
}
class Bird {
get name() {
return "Tweety";
}
}
const car = new Car();
const bird = new Bird();
// 方法中 'this' 的值取决于它们的调用者
car.sayHi(); // Hello from Ferrari
bird.sayHi = car.sayHi;
bird.sayHi(); // Hello from Tweety
// 对于绑定方法,'this' 不依赖于调用者
bird.sayBye = car.sayBye;
bird.sayBye(); // Bye from Ferrari
备注:
类总是在严格模式下。如果一个方法试图访问 this
上的属性,使用未定义的 this
值调用该方法将会抛出错误。
const carSayHi = car.sayHi;
carSayHi(); // TypeError because the 'sayHi' method tries to access 'this.name', but 'this' is undefined in strict mode.
然而,请注意,自动绑定的方法遭受的问题与使用箭头函数作为类属性相同:类的每个实例都会有其方法的自己的副本,这会增加内存使用。只在绝对必要的地方使用它。你也可以模仿 Intl.NumberFormat.prototype.format()
的实现:定义属性作为一个 getter,当访问时返回一个绑定函数并保存它,这样函数只创建一次,并且只会在必要时创建。
with 语句中的 this
尽管 with
语句已被弃用,并且在严格模式下不可用,但它们仍然是正常 this
绑定规则的一个例外。如果在 with
语句中调用了一个函数,并且该函数是作用域对象的属性,那么 this
值会绑定到作用域对象,就好像存在 obj1.
前缀一样。
const obj1 = {
foo() {
return this;
},
};
with (obj1) {
console.log(foo() === obj1); // true
}
规范
Specification |
---|
ECMAScript Language Specification # sec-this-keyword |
浏览器兼容性
BCD tables only load in the browser