这篇文章的产生,是基于冴羽大大的JavaScript 深刻之从 ECMAScript 规范解读 this的思考,这是对应掘金连接,文中详细的论述了来龙去脉,建议各位均可以去了解一下,颇有帮助,而且这篇文章在写做时,也有冴羽大大的帮助,再次表示感谢~javascript
文中的 ES5
规范是参考 颜海镜大大 的译本,也在这里表示感谢。java
那为何还有这篇文章呢?由于不少的同窗在冴羽大大的博客下评论没有看懂,我也是其中的一员,因而我决定要弄明白为何,如今也把个人一些整理分享出来,但愿对你们也有帮助。git
再啰嗦一句,对于知道了各类状况下 this
如何判断的同窗来讲,这篇文章并不会告诉你如何进行 this
指向的判断,更多的是知道为何这样判断,不知足于知其然,更知其因此然。github
Reference Type
(引用类型)开始:Reference Type
:引用类型。在 ES5 文档标准中,将Reference
描述为 a resolved name binding
后端
颜大的 ES5 译本 中,译为已解决的命名绑定。浏览器
resolved
翻译为 已完成
函数
name binding
翻译为 命名绑定
没有任何问题,若是有后端语言经验的同窗可能更好理解。post
那咱们再解释下命名绑定:绑定是有双方的,把 命名
,也就是 咱们取的名字
,要绑定在 某个东西
上面,换言之,就是用 名字
来描述了一个什么 东西
。ui
举个例子: 如今咱们有一个对象:time
,而后他有三个属性:this
time {
second: 32,
minute: 12,
hour: 10
}
复制代码
咱们定义完成后,它必须存在于某一个地方,才能在后面的代码中获取到它。
存在哪由 time
自己的特性来决定,由于它是一个对象,内部的属性是能够添加也能够减小的,换言之,它的大小并不固定。因此咱们把它存在了 堆
里面。
那若是它的大小固定呢?例如 JavaScript
中的 6 种基本类型的值 :null
,undefined
,Boolean
,Number
,String
, Symbol
,既然大小固定,咱们就能够放在 栈
里面。
栈
:程序运行时系统分配的一小块内存,大小在编译期时由编译器参数决定。堆
:能够理解为当前可使用的空闲内存,其大小是须要代码编写的人员本身去申请和释放。(在 JS 中,V8 下有自动垃圾回收机制不须要咱们本身操做) [这里只作简单解释,有须要能够自行 Google 更多信息]Okay。若是你已经理解了咱们的 time
是存在堆中的,那就很好理解了。如今我要用到 time
里面 second
属性的值, 咱们都知道用 time.second
就能够拿到,可是为何 time.second
或者 time[’second’]
就能够访问到 second
属性的值呢?
看起来这个问题很蠢是否是,哈哈,可是仔细想一想,按理来讲:这个值是在内存里面的一小块上面,那咱们须要找到这一块内存,才能取到这个值啊。
如今就很好理解了。那其实 time.second
或者 time[’second’]
他们是和内存里面的真正存放 second
的值那个内存位置 是绑定在一块儿的。只要你用到了 time.second
或者 time[’second’]
,那编译器就找到,哦,这就是存在xxxxx
地址里面的值也就是 32
。
Reference Type
和 this
有什么关系?this
在 Javascript
中一直是一个初学者难以理解的点,有一些甚至写了 2 年的项目也没搞明白为何 this
有这样那样的不一样。
这里咱们先不从使用的场景上来看 this
的指向,仍是回归到本源。
站在编译器的角度,是怎么样去理解 this
指向呢?由于this
的指向的判断,经常发生于函数的调用中,那咱们就来看看ES5 文档标准中的 11.2.3 Function Calls
(函数调用)。
一共分为 8 个步骤:
1. Let ref be the result of evaluating MemberExpression.
2. Let func be GetValue(ref).
3. Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4).
4. If Type(func) is not Object, throw a TypeError exception.
5. If IsCallable(func) is false, throw a TypeError exception.
6. If Type(ref) is Reference, then
a.If IsPropertyReference(ref) is true, then
i.Let thisValue be GetBase(ref).
b. Else, the base of ref is an Environment Record
i.Let thisValue be the result of calling the Implicit This Value concrete method of GetBase(ref).
7. Else, Type(ref) is not Reference.
a.Let thisValue be undefined.
8. Return the result of calling the [[Call]] internal method on func, providing thisValue as the this value and providing the list argList as the argument values.
复制代码
我就不翻译了,由于就算翻译出来你可能也读得很累,那么咱们用图来看下这个流程会更加直观。
我已经把最关键的几个步骤都标红了,若是在第三步返回的 func
没法经过 5
的判断的话,根本就没有讨论 this
指向的必要。
因此咱们重点看:这个里面最关键的点,第 2
, 6
, 7
步骤:
第 2
步:计算 MemberExpression
的值而且赋值给 Ref
: 也就是计算 ()
左边的内容的结果,并赋值给 Ref
。换句话说: Ref
就是对于 ()
左边的内容进行计算以后的引用。
第 6
步:判断 ref 是否为 Reference 类型: 这个没什么好说的。
第 7
步:判断 ref 是不是属性引用类型: 官方解释: 经过执行IsPropertyReference(V)
来判断的,若是基值是个对象或 HasPrimitiveBase(V)
是 true,那么返回 true;不然返回 false。 HasPrimitiveBase(V)
:若是基值是 Boolean, String, Number,那么返回 true。 换成大白话,取决于Ref
这个引用是基于谁的? 若是它基于一个对象
或者 Boolean
, String
, Number
那就返回 true
不然返回 false
。
OK 看到这里,估计你也有些累,可是最关键的部分在下面。
this
的 N 种状况let a = 'm';
function test() {
console.log(this.a);
}
test(); // m
复制代码
咱们用刚刚所看到的 3
个步骤来判断下 this
:
test()
的 Ref
就是 test
引用,它关联到在内存中存储了test()
的某一片断。test()
是否为引用类型 => true
Ref
是不是属性引用类型 => false
,它并无定义在某个引用类型的内部。this = ImplicitThisValue(Ref)
,在 Environment Records
下返回 undefined
,而在非严格模式下,浏览器会把 this
指向 window
提及来很麻烦,其实理解起来很简单。
function test() {
console.log(this.a);
}
let parent = {
a: 's',
test: test
};
parent.test(); // s
复制代码
parent.test()
的 Ref
就是 parent.test
引用,它关联到在内存中存储了test()
的某一片断。parent.test()
是否为引用类型 => true
Ref
是不是属性引用类型 => true
this = GetBase(Ref)
那这个test()
方法是基于谁呢?很明显就是 parent
,因此 this
指向 parent
let a = 'k';
function Foo() {
console.log(this);
}
let c = new Foo();
c.a = 's'
复制代码
new
关键字调用,区别于通常的函数调用,你们能够看下MDN 上的解释,明确的指出了
若是你仍旧想从规范的角度来解释,建议你读一下ES5 规范:11.2.2 The new Operator 以及关联的 ES5 规范:8.7.1 GetValue (V) 我反复了读了不少遍,可是没有发现如何从规范的角度去解释 this
的指向问题,最后也是请教了冴羽大大才知道 new
可能在底层有明确指定 this
的过程,不适合用这样的方式解读,可是,若是你有了更好的答案,很欢迎一块儿讨论~
既然明白了this
指向的是 c
那么输出的是 {a : 's'}
function Foo() {
return () => {
return () => {
console.log(this);
};
};
}
console.log(Foo()()());
复制代码
和 new
同样箭头函数也是一个特例,可是箭头函数一样能够从对应的规范中找到 this
的答案:
建议参考ES6 规范-箭头函数-evaluation里面的一段话:
“An ArrowFunction does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. ”
直译为:"ArrowFunction
不为 arguments
, super
, this
或 new.target
定义本地绑定。 对 ArrowFunction
中的 arguments
, super
, this
或 new.target
的任何引用都必须解析为词法做用域中的绑定。"
也就是说,箭头函数内部不会定义 this
,都是由它外部的词法做用域来决定的,也就是说,箭头函数的外部的 this
指向的是谁,那箭头函数内部的 this
指向的也是谁。
回到这个例子,咱们知道至始至终,不管你套多少层箭头函数,this
都是指向 Foo
里面的 this
,那Foo
里面的 this
根据咱们以前的例子能够知道,就是指向了 window
。
欢迎你们关注个人掘金专栏,后期也会更新更多优质的内容~ 有任何问题,欢迎理性和友好的讨论~
题图来自 unsplash