换个角度看 JavaScript 中的 (this) => { 整理 (JavaScript 深入之从 ECMAScript 规范解读 this ) }
栏目: JavaScript · 发布时间: 6年前
内容简介:这篇文章的产生,是基于冴羽大大的文中的那为什么还有这篇文章呢?因为很多的同学在冴羽大大的博客下评论没有看懂,我也是其中的一员,于是我决定要弄明白为什么,现在也把我的一些整理分享出来,希望对大家也有帮助。
这篇文章的产生,是基于冴羽大大的 JavaScript 深入之从 ECMAScript 规范解读 this 的思考,这是对应掘金链接,文中详细的论述了前因后果,建议各位都可以去了解一下,很有帮助,并且这篇文章在写作时,也有冴羽大大的帮助,再次表示感谢~
文中的 ES5
规范是参考 颜海镜大大 的译本,也在这里表示感谢。
那为什么还有这篇文章呢?因为很多的同学在冴羽大大的博客下评论没有看懂,我也是其中的一员,于是我决定要弄明白为什么,现在也把我的一些整理分享出来,希望对大家也有帮助。
再啰嗦一句,对于知道了各种情况下 this
如何判断的同学来说,这篇文章并不会告诉你如何进行 this
指向的判断,更多的是知道为什么这样判断,不满足于知其然,更知其所以然。
一. 从 Reference Type
(引用类型)开始:
Reference Type
:引用类型。在ES5 文档标准中,将 Reference
描述为 a resolved name binding
颜大的 ES5 译本 中,译为已解决的命名绑定。
-
resolved
翻译为已完成
-
name binding
翻译为命名绑定
没有任何问题,如果有后端语言经验的同学可能更好理解。
那我们再解释下命名绑定:绑定是有双方的,把 命名
,也就是 我们取的名字
,要绑定在 某个东西
上面,换言之,就是用 名字
来描述了一个什么 东西
。
1.为什么需要用一个名字来描述,它没有自己本身的名字吗?
举个例子: 现在我们有一个对象: time
,然后他有三个属性:
time () { second: 32, minute: 12, hour: 10, } 复制代码
2.这个对象是存在什么地方的?
我们定义完成后,它必须存在于某一个地方,才能在后面的代码中获取到它。
存在哪由 time
本身的特性来决定,因为它是一个对象,内部的属性是可以添加也可以减少的,换言之, 它的大小并不固定 。所以我们把它存在了 堆
里面。
那 如果它的大小固定 呢?例如 JavaScript
中的 6 种基本类型的值 : null
, undefined
, Boolean
, Number
, String
, Symbol
,既然大小固定,我们就可以放在 栈
里面。
3.堆栈是什么?为什么不同类型的数据要分开放呢?
栈 堆
4.引用类型存在堆中,和例子有什么关系?
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 种情况
1.直接调用
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
说起来很麻烦,其实理解起来很简单。
2.在对象内部调用
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
3.new 关键字
let a = 'k'; function Foo() { console.log(this.a); } let c = new Foo(); c.a = 's'; 复制代码
new
关键字调用,区别于一般的函数调用,大家可以看下MDN 上的解释,明确的指出了
- 一个继承自 Foo.prototype 的新对象被创建。
- 使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象
如果你仍旧想从规范的角度来解释,建议你读一下 ES5 规范:11.2.2 The new Operator 以及关联的 ES5 规范:8.7.1 GetValue (V) 我反复了读了很多遍,但是没有发现如何从规范的角度去解释 this
的指向问题,最后也是请教了冴羽大大才知道 new
可能在底层有明确指定 this
的过程,不适合用这样的方式解读,但是,如果你有了更好的答案,很欢迎一起讨论~
既然明白了 this
指向的是 c
那么输出肯定是 s
4.箭头函数
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
。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 从傅里叶分析角度解读深度学习的泛化能力
- 破解面试难题8个角度带你解读SQL面试技巧!
- 从责任界定和问题预警角度 解读全栈溯源对DevOps的价值
- 个人角度阐述 OKR
- 全局角度出发讨论敏捷
- 技术角度聊聊比特币减半
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
游戏人工智能编程案例精粹
巴克兰德 (Mat Buckland) / 罗岱 / 人民邮电出版社 / 2008年06月 / 55.00元
《游戏人工智能编程案例精粹》适合对游戏AI开发感兴趣的爱好者和游戏AI开发人员阅读和参考。一起来看看 《游戏人工智能编程案例精粹》 这本书的介绍吧!