每个 JavaScript 程序猿,包括我自己,都一直在努力了解 this
关键字在代码中的真正身份。
我设计了一个通用算法,可以帮你在任何情况下确定 this
关键字的值。虽然我尽可能的使算法容易看懂,但还是建议你多看几遍并理解相关术语。
另外还用了几个例子展示怎样用这个算法一步一步的对 this
进行评估,最后你自己亲自试一试。
1. this 算法
把算法定义为 ThisValueOfFunction(func, invocationType)
,返回值为在以 invocationtype
方式调用函数 func
时的 this
值:
ThisValueOfFunction(func, invocationType):
如果
func
是一个箭头函数,那么- 如果
func
是在最外面的作用域 中定义的,那么返回globalObject
否则
SuffeFunc
是Func
的外部函数- 返回
ThisValueOfFunction(outerFunc, outerInvocationType)
- 如果
如果
func
是originFunc
函数的绑定函数,那么thisArg
是Func = OriginFunc.bind(thisarg)
的参数- 返回
thisArg
如果
func
是someclass
类 中的constructor()
方法,那么instance
是instance = new SomeClass()
的实例- 返回
instance
如果
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();
ThisValueOfFunction(myFunc, “常规的”)
myfunc
是箭头函数:从而在算法中匹配情况 1。同时 myFunc
在最外面的作用域内定义,匹配情况 1.1。
算法 1.1 中返回 globalObject
意思是 myFunc
中的 this
值为全局对象 window
(在浏览器环境中)。
例 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
值?写出你的判断步骤。
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现