一个能够确定 this 值的算法
每个 JavaScript 程序猿,包括我自己,都一直在努力了解 this 关键字在代码中的真正身份。
我设计了一个通用算法,可以帮你在任何情况下确定 this 关键字的值。虽然我尽可能的使算法容易看懂,但还是建议你多看几遍并理解相关术语。
另外还用了几个例子展示怎样用这个算法一步一步的对 this 进行评估,最后你自己亲自试一试。
1. this 算法
把算法定义为 ThisValueOfFunction(func, invocationType) ,返回值为在以 invocationtype 方式调用函数 func 时的 this 值:
ThisValueOfFunction(func, invocationType):
1、如果 func 是一个箭头函数,那么
如果 func 是在最外面的作用域 中定义的,那么返回 globalObject
否则
SuffeFunc 是 Func 的外部函数
返回 ThisValueOfFunction(outerFunc, outerInvocationType)
2、如果 func 是 originFunc 函数的绑定函数,那么
thisArg 是 Func = OriginFunc.bind(thisarg) 的参数
返回 thisArg
3、如果 func 是 someclass 类 中的 constructor() 方法,那么
instance 是 instance = new SomeClass() 的实例
返回 instance
4、如果 func 是一个常规函数,那么
如果 invocationtype 是作为构造函数,那么
newObject 是新构造的对象 newObject = new func()
返回 newObject
如果 invocationtype 是间接调用的,那么
thisArg 是 func.call(thisArg) 或 func.apply(thisArg) 的参数
返回 thisArg
如果 invocationtype 是 方法,那么
object 是在 object.func() 上调用 func 的对象
返回 object
如果 invocationtype 是常规的,那么
如果启用了严格的模式,那么返回 undefined
否则返回 globalObject
1.1 算法中使用的术语
这个算法使用了大量的 JavaScript 术语。如果你不熟悉某些东西,先看下面的解释。
- 箭头函数
箭头函数是使用粗箭头语法 => 定义的函数。 箭头函数示例:
const sum = (number1, number2) => {
return number1 + number2;
}
- 绑定函数
绑定函数是通过在函数上调用方法 myFunc.bind(thisArg, arg1, ..., argN)创建的函数。 绑定函数的示例:
function originalFunction() {
// ...
}
const boundFunction = originalFunction.bind({ prop: 'Value' });
- 常规函数
常规函数是用 function 关键字或在对象上定义的简单 JavaScript 函数。 常规函数的示例:
function regularFunction(who) {
return `Hello, ${who}!`;
}
const object = {
anotherRegularFunction(who) {
return `Good bye, ${who}!`
}
};
- constructor()
constructor() 是 class 内部的一种特殊方法,用于初始化类实例。
class SomeClass() {
constructor(prop) {
this.prop = prop;
}
}
- 最外部的作用域
最外部的作用域是没有外部作用域的最顶级作用域。
// 最外部的作用域
let a = 1;
function someFunction() {
// someFunction() 的作用域
// 这里不是最外部的作用域
let b = 1;
}
- 外部函数
外部函数在其作用域内包含另一个函数。
// outerFunction() 是 myFunction() 的外部函数
function outerFunction() {
function myFunction() {
//...
}
}
- 全局对象全局对象是在全局作用域内始终存在的对象。 window 是浏览器环境中的全局对象,在 Node 环境中是 global。
- 调用
函数的调用只是使用一些参数来调用该函数。function sum(number1, number2) { return number1 + number2; } sum(1, 3); // 调用 sum.call({}, 3, 4); // 调用 sum.apply({}, 5, 9); // 调用 const obj = { method() { return 'Some method'; } }; obj.method(); // 调用 class SomeClass { constructor(prop) { this.prop = prop; } } const instance = new SomeClass('Value'); // 调用
- 构造函数调用
使用 new 关键字调用函数或类时,将发生构造函数调用。function MyCat(name) { this.name = name; } const fluffy = new MyCat('Fluffy'); // 构造函数调用 class MyDog { constructor(name) { this.name = name; } } const rex = new MyDog('Rex'); // 构造函数调用
- 间接调用
使用 func.call(thisArg, ...) 或 func.apply(thisArg, ...) 方法调用函数时,会发生间接调用。function sum(number1, number2) { return number1 + number2; } sum.call({}, 1, 2); // 间接调用 sum.apply({}, 3, 5); // 间接调用
- 方法调用
当在属性访问器表达式 object.method() 中调用函数时,将发生方法调用。const object = { greeting(who) { return `Hello, ${who}!` } }; object.greeting('World'); // 方法调用 object['greeting']('World'); // 方法调用
- 常规调用
只用函数参数变量调用 func(...) 时,会发生常规调用。function sum(number1, number2) { return number1 + number2; } sum(1, 4); // 常规调用
- 严格模式
严格模式是对运行 JavaScript 代码有特殊限制的一种特殊模式。 通过在脚本的开头或函数作用域的顶部添加 use strict 指令来启用严格模式。
2.例子
例 1
const myFunc = () => {
console.log(this); // logs `window`};
myFunc();
例 2
const object = {
method() {
console.log(this); // logs { method() {...} } }
};
object.method();
ThisValueOfFunction(object.method, “作为方法调用”)
method() 同时是 object 的属性,是常规函数。与算法的情况 4 匹配。
object.method() 是一种方法调用,因为是属性访问的,送一因此与 4.3 匹配。
然后,根据 4.3,method() 方法中的 this 等于方法的拥有者 (object.method()) — object。
例 3
function MyCat(name) {
this.name = name;
const getName = () => {
console.log(this); // logs { name: 'Fluffy', getName() {...} } return this.name;
}
this.getName = getName;
}
const fluffy = new MyCat('Fluffy');
fluffy.getName();
ThisValueOfFunction(getName, “作为方法调用”)
getName() 是一个箭头函数,所以符合算法的情况 1;因为 mycat 是 getName()的外部函数,然后与 1.2 匹配。
分支 1.2.2 :this 在 getName() 箭头函数内部的值等于外部函数的值 MyCat。
所以让我们在 MyCat 函数上运行算法 ThisValueOfFunction(MyCat, "做为构造函数") 。
ThisValueOfFunction(MyCat, “作为构造函数”)
MyCat 是常规函数,所以跳转到算法的分支 4。
因为 MyCat 做为构造函数调用 new MyCat('Fluffy'),符合分支 4.1。最后根据 4.1.1 和 4.1.2,this 在 MyCat 中等于构造的对象:fluffy。
然后,返回箭头函数后符合 1.2.2,在 getname() 中的 this 等于 mycat 的 this,最终结果为 fluffy。
3. 练习
要理解这个算法,最好自己亲自试试。下面是 3 个练习。
练习 1
const myRegularFunc = function() {
console.log(this); // logs ???};
myRegularFunc();
如何确定 myRegularFunc() 中的 this 值?写出你的判断步骤。
练习 2
class MyCat {
constructor(name) {
this.name = name;
console.log(this); // logs ??? }
}
const myCat = new MyCat('Lucy');
如何确定 new MyCat('Lucy') 中的 this 值?写出你的判断步骤。
练习3
const object = {
name: 'Batman',
getName() {
const arrow = () => {
console.log(this); // logs ??? return this.name;
};
return arrow();
};
}
object.getName();
如何确定 arrow() 中的 this 值?写出你的判断步骤。