引文
在前面的文章里讨论了执行上下文,对于每个执行上下文都有三个重要的属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
本篇文章重点讲讲this相关内容
正文
MDN中关于this是这样定义的:🔗
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)➀,this 不能在执行期间被赋值,并且在每次函数被调用时 this 的值也可能会不同(取决于不同的调用方式)➁
也就是说:this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式,因为:
接下来依据 ECMAScript 2023 规范(ECMA-262 第13版)的定义,不同的上下文环境下this分为下面几种:
1. 全局上下文
在全局执行上下文中,this 引用全局对象。
19.1.1 Global Object
The global object is used as the this value when entering the top-level code from the execution context.
2. 函数调用(默认绑定)
函数的 this 绑定根据严格模式与否有不同的定义:
非严格模式(non-strict mode)
// 在非严格模式下的普通函数调用,this 被解析为全局对象
function foo() {
console.log(this);
}
foo(); // 全局对象(浏览器中是 window,Node.js 中是 global)
严格模式(strict mode)
'use strict';
// 在严格模式下的普通函数调用,this 被解析为 undefined
function foo() {
console.log(this);
}
foo(); // undefined
规范
10.4.3.3 This Value
When a function code is executed, the this value is determined in the following way:
1. If the function is in strict mode:
a. If this is not defined, then undefined.
b. If this is defined, then this.
2. If the function is not in strict mode:
a. If this is not defined, then the global object.
b. If this is defined, then ToObject(this).
3. 对象的方法调用(隐式绑定)
当一个函数作为对象的方法被调用时,this 被绑定到该对象。
let obj = {
value: 42,
method: function() {
console.log(this.value);
}
};
obj.method(); // 42
9.2.1 [[Call]] ( thisArgument, argumentsList )
1. Let func be the function object.
2. Let thisValue be the thisArgument provided by the caller.
3. Let context be the running execution context.
4. Set the context's ThisBinding to thisValue.
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
4. call 和 apply 调用(显式绑定)
使用 Function.prototype.call 和 `Function.prototype.apply``方法调用函数时,this 绑定到提供的第一个参数。
function greet() {
console.log(this.name);
}
let user = { name: 'Bob' };
greet.call(user); // Bob
greet.apply(user); // Bob
4. 构造函数调用(new绑定)
通过new关键字调用构造函数时,this绑定到新创建的对象。
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // Alice
9.2.2 [[Construct]] (argumentsList, newTarget)
Let thisArgument be OrdinaryCreateFromConstructor(F, "%Object.prototype%", intrinsics, [[ObjectConstructor]]).
When a constructor is called using `new`, a new object is created and `this` is bound to the new object.
6. 箭头函数
箭头函数不会创建自己的this绑定,this取决于包含它的上下文。
let obj = {
value: 42,
arrowFunc: () => {
console.log(this.value);
}
};
obj.arrowFunc(); // undefined, 因为箭头函数的this被绑定到定义时的上下文(在此例中为全局对象)
19.2.1.1 Function Initialization
A arrow function does not have its own this binding.
The value of this inside an arrow function is determined by the enclosing scope.
this优先级比较
在上面提到的四条绑定规则(默认绑定、隐式绑定、显式绑定、new绑定)中,毫无疑问默认绑定的优先级最低,我们暂时不考虑它,先来比较一下隐式绑定和显式绑定的优先级:
function foo () {
console.log(this.a);
}
var obj1 = {
a: 2,
fn: foo
}
var obj2 = {
a: 3,
fn: foo
}
// 隐式绑定
obj1.fn(); // 2
obj2.fn(); // 3
// 显式绑定
obj1.fn.call(obj2) // 3
obj2.fn.call(obj1) // 2
可以看到显式绑定优先级更高,因此在应用时可以优先考虑使用显式绑定
那么new绑定和显式绑定谁的优先级会更高呢?
由于new和call/apply不能一起使用,因此不能通过new Foo.call(obj)的方式直接比较两者的优先级,但是可以通过硬绑定(bind)间接比较两者的优先级
function Foo () {
this.value = 33;
}
var obj = { value: 22 };
var boundFoo = Foo.bind(obj); // boundFoo 是一个使用 bind 预先绑定了 obj 的函数
var instance = new boundFoo();
// 当我们通过 new 操作符调用 boundFoo 时,虽然 boundFoo 初始绑定到 obj,但因为 new 绑定的优先级更高,所以 this 最终指向新创建的实例,而不是 obj
console.log(instance.value); // 33 证明 new 绑定具有更高优先级
console.log(obj.value); // 22
通过比较:new绑定的优先级更高
优先级比较总结
new绑定:通过new操作符调用的构造函数,this绑定到新创建的对象。- 显式绑定:通过
call、apply或bind调用所指定的对象。 - 隐式绑定:作为对象方法调用时,
this绑定到调用的对象。 - 默认绑定:没有绑定到任何对象时,非严格模式下绑定到全局对象,严格模式下绑定到
undefined
##总结: 本文内容主要对对this指向和优先级做了比较分析
- this主要分为全局上下文中的this、默认绑定、隐式绑定、显式绑定、new绑定和箭头函数中这6种
- this优先级 new绑定 > 显示绑定 > 隐式绑定 > 默认绑定