加餐 | 让JavaScript运行起来
该思维导图由 AI 生成,仅供参考
从文本到脚本
引用与静态语言的处理
结构与体系的回顾
模块一:体系 1
模块二:体系 2
模块三:体系 3
最后
- 深入了解
- 翻译
- 解释
- 总结
JavaScript的运行机制是一门复杂而精密的编程语言的核心。本文从底层和表面的角度深入解析了JavaScript的运行原理。在底层,JavaScript脚本代码被处理为记号,这是其运行的初始阶段。而在表面上,引用是静态语言与引擎之间的桥梁,是整个叙述框架的核心。文章还强调了JavaScript语句执行的本质是表达式计算,函数调用也是表达式执行的一种。在整个运行机制中,引用的作用是指向值、代表值的一个概念。通过对JavaScript整个运行机制的解析,读者可以更深入地了解JavaScript的内部运行原理,从而更好地应用和理解这门语言。 在模块一中,文章提出了语言的三个层面的概念:记号、引用、值、表达式、语句、名字、环境/作用域等。这些概念基本上都是在“代码的静态组织”过程中就完成/实现了的。在模块二中,文章讨论了表达式执行、函数执行、函数执行的扩展等概念,并在规范层面统一了“表达式执行和函数执行”。模块三则回顾了前两大模块的内容,强调了“抽象的语言”如何处理“物理的代码”。 总的来说,本文通过深入解析JavaScript的运行机制,帮助读者更好地理解这门语言的内部运行原理,为读者提供了全面而深入的技术视角。
2019-12-09给文章提建议
《JavaScript 核心原理解析》,新⼈⾸单¥59
全部留言(16)
- 最新
- 精选
- kkxue个人觉得老师说明下您理解编程语言背后的哲学逻辑或者体系,我们上道才更快。
作者回复: 关于这个,还真的是有的。请参考如下: > https://github.com/aimingoo/my-ebooks 其中的《程序原本》一书,是可以自由下载分享的电子书。
2020-05-2123 - westfall“所以本质上,引用还是指向值、代表值的一个概念,它只是“获得值的访问能力”的一个途径。最终的结果仍然指向原点:计算值、求值。” 不明白引用与指针的区别。
作者回复: 引用是在“设计语言的规范”中用的,而指针是“程序代码”中用的。 在js中的“引用与值”,与在ECMAScript规范中的“引用与值”不是相同的概念。
2020-06-092 - 刘长锋D2 大会上听老师说,要有新书发布,很是期待!
作者回复: 谢谢🙏已经交稿了,在等出版社~可这不肺炎疫情闹的嘛😳
2020-03-122 - K4SHIFZ老师,下个课程什么时候出啊,迫不及待想学了
作者回复: :) 这个真的要等很久可能。^^.
2020-03-2521 - Kids See Ghost关于GetValue最后再请教一下 规范里的是 ``` 6.2.4.5 GetValue ( V ) The abstract operation GetValue takes argument V. It performs the following steps when called: 1. ReturnIfAbrupt(V). 2. If V is not a Reference Record, return V. 3. If IsUnresolvableReference(V) is true, throw a ReferenceError exception. 4. If IsPropertyReference(V) is true, then a. Let baseObj be ? ToObject(V.[[Base]]). b. If IsPrivateReference(V) is true, then i. Return ? PrivateGet(baseObj, V.[[ReferencedName]]). c. Return ? baseObj.[[Get]](V.[[ReferencedName]], GetThisValue(V)). 5. Else, a. Let base be V.[[Base]]. b. Assert: base is an Environment Record. c. Return ? base.GetBindingValue(V.[[ReferencedName]], V.[[Strict]]) (see 9.1). ``` 我以为这个被get的value就是跟base有关。我看了您说的prepack-core里的代码,这部分貌似是叫_dereference。代码大概是 ``` _dereference(realm: Realm, V: Reference | Value, deferenceConditionals?: boolean = true): Value { // This step is not necessary as we propagate completions with exceptions. // 1. ReturnIfAbrupt(V). // 2. If Type(V) is not Reference, return V. if (!(V instanceof Reference)) return V; // 3. Let base be GetBase(V). let base = this.GetBase(realm, V); //.... if (this.HasPrimitiveBase(realm, V)) { // i. Assert: In this case, base will never be null or undefined. invariant(base instanceof Value && !HasSomeCompatibleType(base, UndefinedValue, NullValue)); // ii. Let base be To.ToObject(base). base = To.ToObject(realm, base); } invariant(base instanceof ObjectValue || base instanceof AbstractObjectValue); // b. Return ? base.[[Get]](GetReferencedName(V), GetThisValue(V)). return base.$GetPartial(this.GetReferencedNamePartial(realm, V), GetThisValue(realm, V)); ``` 这里怎么看都是跟base有关系。实在是看不懂到底怎么样把一个value用getValue取出来。您的课也我也没有看到有直接讲到这个。所以能不能麻烦最后再指点一下,到底规范里面的哪一步是把像obj = {x: 'abcdef'},当【obj.x】作为一个引用时,我们把字符串给用getValue取出来的?
作者回复: 对于这个例子,由于是obj.x,所以r.Base指向obj,r.ReferencedName指向'x'。 然后在第2~3步的判断中都是false,直到第4步,IsPropertyReference(r)将判断为True,这样会在4.a中r.Base中取出对象obj,置入baseObj。 接下来到4.c,会调用 baseObj.[[Get]](V.[[ReferencedName]], GetThisValue(V)) 也就是说,会调用obj的内部方法槽[[Get]],然后传入name和this。 来到对象标准的内部方法[[Get]]的定义,在这里: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver 注意按照上面的传参,这里的P就是name,就是GetReferencedName(V)。于是进入OrdinaryGet(),在这里你能看到一个过程,就是地扫描obj或递归它的parent的属性表,直到找到P所传入的这个名字'x'(记得原型链上怎么读属性obj.x吧?)。 找到P对应的名字'x'之后,检测一下,如果它是数据属性,就返回这个对象的属性列表中的值,亦即是return desc.[[Value]]. 否则就调用存取器方法desc[[Get]]来取。——记得属性有两种声明方式吧? 所以,对于obj.x来说,调用GetValue(r)的过程,最终会变成: ``` obj = r[[Base]] name = r[[ReferencedName]] // 'x' desc = obj[name] 的属性描述符 ``` 并最终取到desc.[[Value]]的结果,例如你之前例子中的"abcdef"。
2022-01-17 - Kids See Ghost再请教一下:在JS里做equality check的时候, 比如用 === - 这个时候我们是在比value还是比reference?也就是说 x === y - 等于是 getValue(x) === getValue(y) 吗? 相应地,是不是说在JS里面,能拿来比较的东西只能是 value,两个reference是肯定不一样的? 请问规范里面有相应的章节吗?
作者回复: 比值。所以是getValue(x) === getValue(y) 。规范中在这里: https://tc39.es/ecma262/#sec-equality-operators-runtime-semantics-evaluation 你自己看,确实就是对两个操作数都做了GetValue。 另外,你对GetValue的理解是错的。我在另一个回复里说。 再另外,要自己查规范。慢慢看,你会找到的,不要偷懒。
2022-01-17 - Kids See Ghost1. 请教一下,您说“例如obj = {x: 'abcdef'},当【obj.x】作为一个引用时,base是obj,而不是那个字符串'abcdef'。又例如全局的变量let x = 'abcdef',base将是全局词法环境,指向Global.lexEnv。” 假如我现在把obj.x赋值给一个变量,那getValue(obj.x)拿出来的值不应该是字符串'abcdef'吗?https://tc39.es/ecma262/#sec-getvalue 我理解的规范里的getValue 最终拿出来的value就是base,请问这个理解是错的吗?如果是错的,那getValue里拿出来的value到底是什么? 换一个问法,如果当【obj.x】作为一个引用时,错误地把base是当成了那个字符串'abcdef',会造成什么样的理解错误呢,请问您能举个例子吗? 2. 另外想请教一下在https://tc39.es/ecma262/#sec-reference-record-specification-type 里定义的 Reference Record Specification Type里,[[ReferencedName]] 提到 "The name of the binding. Always a String if [[Base]] value is an Environment Record."这句话的意思是什么?意思是如果base不是一个Environment Record,那ReferencedName可以不是string?请问Environment Record又是啥。。
作者回复: 规范里的getValue 最终拿出来的value就是base,请问这个理解是错的吗?如果是错的,那getValue里拿出来的value到底是什么? ------- 当然是错的。“引用(规范类型)”里面根本不保存value,它没有value字段,也不存放值的内容。它是一个值的索引器,所以你应该仔细读GetValue()这个内部过程,才能弄明白如何利用这个东西找到“每一种不同类型的引用”所对应的值。 同样的原因,base肯定不可能“错误的理解为那个字符串”。那个字符串根本不会在“引用(规范类型)”这个记录的任何地方。 仔细读规范。另外,如果你确实对规范理解有疑问,我建议你自己git一个prepack-core项目下来,然后在IDE环境(例如vscode)里调试来看。之所以推荐prepack-core,是因为它是按照规范逐行翻译成js代码的,读起来易懂。prepack-core项目在这里: https://github.com/aimingoo/prepack-core Environment Record~~ 看规范吧。里面有,是非常关键的类型。或者我之前给过你的视频地址里也有。在这里: https://www.bilibili.com/video/BV1Wy4y1b7PG 如果你才开始读规范,那么至少也要先把规范的大概目录看完,知道它怎么写以及有什么吧。这些东西在结构上看明白大概也得花些功夫,带着问题先看吧,不要着急找答案。问题没问对,答案是没意义的;问题问对了,答案就几乎跃跃欲出了。
2022-01-17 - Kids See Ghost几个问题想向老师请教一下: 1. Reference Record Specification Type (https://tc39.es/ecma262/#sec-reference-record-specification-type)就是您一直提到的“规范层面的引用(References)”吗? 2. 引用reference里的Base,我们是可以理解为真正的那个数字/字符串/object etc. 吗?而不是指针/引用(内存层面上的)或者了另一个reference? 3. 最想问的一个问题是,想知道在call function的时候,规范里有规定是pass by value还是pass by reference吗?假如说我们有 ``` function fn(x) {} const a = {} const b = 1 fn(a) fn(b) ``` 这里第一次给fn传入`a`的时候,整个过程我们有`x = getValue(a)` 吗还是说我们直接把`a`的reference给了`x`? fn(b) 也一样吗? 我在规范里找了很久都找不到相应的章节,请问您能给一个链接到专门讲**函数传参**的章节吗? 4.最后,您说“这个框架的核心在于——ECMAScript 的目的是描述“引擎如何实现”,而不是“描述语言是什么”。”这句话确定没有说反了吗?难道不是 ECMAScript的目的是设计一个语言,具体语言怎么实现是javascript engine的事情。比如ECMAScript里面不会讲memory layout应该会怎么样,什么东西放到stack上,什么放到heap上,因为这个是implementation details,由引擎自己决定。 感谢!
作者回复: 1. 是。 2. 不是。例如obj = {x: 'abcdef'},当【obj.x】作为一个引用时,base是obj,而不是那个字符串'abcdef'。又例如全局的变量let x = 'abcdef',base将是全局词法环境,指向Global.lexEnv。 3. 是传值。例如f(obj.x),那么实际传入的是f(GetValue(obj.x))。在执行Call()内部运算之前,传入参数列表就被处理过了。规范上也讲了,只是一个叙述。如下: https://tc39.es/ecma262/#sec-call 原文:and argumentsList is the value passed to the corresponding argument of the internal method. 4. 没说反。ECMAScript当然也描述了语言是什么样子,比如语法、静态词法等等,但这在规范中十不占一。真正多的内容是如何实现它。ECMAScript当然没讲你说的这些,因为那些是那些是执行环境。——“引擎如何实现一个语言”不单单是说栈如何操作、内存如何分配,也包括一个具体的“+”运算符如何实现,对吧?ECMAScript只约定了“+”如何实现,没说它的运算数在栈上如何分配,很正常啊,具体执行环境实现的时候用不用栈都还另说呢。
2022-01-15 - 青史成灰老师,关于上面“为什么要有‘引用’这么个东西呢”的解释,读下来感觉和C++的指针很像,指针是内存的地址,指向堆内存中的对象,需要访问指针指向的成员时,直接解引用这个指针,v = *p,就和此处的x=GetValue(r)一样。 不知道这样理解是否正确?
作者回复: 这是不一样的。 在第一讲的回复内容中,我给leslee的回复里面讲过“JavaScript中的引用”,与“ECMAScript中的引用”不是同一个东西。你这里所谈的指针概念,与“JavaScript中的引用”类似,它的细节和作用,你可以看看上面这一讲关于leslee的回复。 另外,在给Smallfly的回复中,我详细讲了ECMAScript中的引用是怎样的一个结构。你也可以阅读一下。 在这里:https://time.geekbang.org/column/article/164312 (不过因为这个工具的设计问题,我没办法直接指到他们的评论回复,请查找一下)
2019-12-142 - 行问eval(str) 是执行语句,而{$str}是执行表达式 这里是 {$str} 正确,还是 ${str} 正确?
作者回复: 这里是有排版错误,后一个是`${str}`。我的意思是,同一个字符串(仅指它的字面文本),这里是它作为语句和表达式执行的两种方式。但是,如果str理解成“变量”,而不是“变量的字面文本”,那么就不是我的原意了。
2019-12-092