JavaScript中的this是当前执行上下文(global、function、eval)的一个属性。在非严格模式下,总是指向一个对象,在严格模式下可以是任何值。
全局上下文
无论是否在严格模式下,在全局执行环境中this都指向全局对象
console.log(this === window) // true
函数上下文
在多数情况下,this指向的对象通常由函数的调用方式所决定。this不能在执行期间被赋值,并且在每次函数调用时this的值也可能不同。
在非严格模式下,且没有被设置,this指向全局对象(浏览器中为window对象,node环境下为globalThis对象)
function bar() {
console.log(this === window)
}
bar();
在严格模式下,如果在进入执行环境时没有设置this值,this为undefined。
function bar() {
'use strict'
console.log(this === window)
}
bar();
更改函数this指向
如果想要把this的值从一个环境传到另一个,就需要使用call或apply方法。
** call\apply 方法 **
在非严格模式下使用 call 和 apply 时,如果用作 this 的值不是对象,则会被尝试转换为对象。null 和 undefined 被转换为全局对象。原始值如 7 或 'foo' 会使用相应构造函数转换为对象。因此 7 会被转换为 new Number(7) 生成的对象,字符串 'foo' 会转换为 new String('foo') 生成的对象。
function add(c, d) {
return this.a + this.b + c + d;
}
const o = {a: 1, b: 3}
// 第一个参数是用作“this”的对象
// 其余参数作为函数参数
add.call(o, 5, 7)
// 第一个参数作为“this”的对象
// 第二个参数是一个数组,数组中的两个成员用作函数参数
add.apply(o, [10, 20])
** bind 方法 **
es5中引入Function.prototype.bind()。调用func.bind({})会创建一个与func具有相同函数体和作用域的函数,但是在新的函数中this将永久绑定到第一个参数上。
function func() {
console.log(this);
}
const newFunc = func.bind({name: 'alex', age: 22})
newFunc()
** 箭头函数 **
在箭头函数中,this与封闭词法环境的this保持一致。
在全局代码中,它将被设置为全局对象:
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
如果将this传递给call、bind、或者apply来调用箭头函数,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数(thisArg)应该设置为null。
// 接着上面的代码
// 作为对象的一个方法调用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 尝试使用call来设定this
console.log(foo.call(obj) === globalObject); // true
// 尝试使用bind来设定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
在其他函数内创建的箭头函数的this被设置为封闭的词法环境的。
// 创建一个含有bar方法的obj对象,
// bar返回一个函数,
// 这个函数返回this,
// 这个返回的函数是以箭头函数创建的,
// 所以它的this被永久绑定到了它外层函数的this。
// bar的值可以在调用中设置,这反过来又设置了返回函数的值。
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// 将返回的函数的引用赋值给fn。
var fn = obj.bar();
// 直接调用fn而不设置this,
// 通常(即不使用箭头函数的情况)默认为全局对象
// 若在严格模式则为undefined
console.log(fn() === obj); // true
特殊情况
// 但是注意,如果你只是引用obj的方法,
// 而没有调用它
var fn2 = obj.bar;
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window); // true
类上下文
在类的构造函数中,this是一个常规对象。类中所有非静态属性或方法都会配添加到this的原型中:
class Foo {
constructor() {
const proto = Object.getPrototypeOf(this)
console.log(Object.getOwnPropertypeNames(proto)
this.foo = this.foo.bind(this)
}
bar() {
console.log(this)
}
foo() {
console.log(this)
}
}
new Foo() // ['constructor', 'bar']
** 对象方法中的this ** 当函数作为对象里的方法被调用时,this 被设置为调用该函数的对象。这样的行为完全不会受函数定义方式或位置的影响。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
// this 的绑定只受最接近的成员引用的影响
o.b = {g: independent, prop: 42};
console.log(o.b.g()); // 42
原型链中的this
对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就像该方法就在这个对象上一样。
class Obj {
constructor() {
this.a = 10
this.b = 20
this.c = 30
}
}
Obj.prototype.func = function() {
return this.a + this.b + this.c
}
const o = new Obj()
console.log(o.func()) // 60
getter 与 setter 中的 this
function sum() {
return this.a + this.b + this.c
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return this.sum / 3
}
};
Object.defineProperty(o, 'sum', {
get: sum,
enumerable: true,
configurable: true
})
console.log(o.average, o.sum) // 2, 6
派生类
派生类的构造函数没有初始的this绑定。在构造函数中调用super()方法会生成一个this绑定,并相当于执行以下代码。在调用super()方法之前引用this会抛出错误。
this = new Foo()
派生类中的构造函数必须调用super()方法或者返回一个对象,否则会报错。
class Foo {}
// 正确
class Bar extends Foo {}
// 正确
class Bar extends Foo {
constructor() {
super()
}
}
// 正确
class Bar extends Foo {
constructor() {
return {}
}
}
// 错误
class Bar extends Foo {
constructor() {}
}
其他
在DOM事件处理函数中的this指向
当函数被用作事件处理函数时,它的 this 指向触发事件的元素
document.querySelector('#app').addEventListener('click', function(e) {
console(this == e.currentTarget)
})
内联事件处理函数中的this指向
当代码被内联 on-event 处理函数 调用时,它的this指向监听器所在的DOM元素
<button onclick="console.log(this)">click me</button>