作者回复: +1 ^^.
作者回复: 多谢。我在后面的课程中尽量注意这个 ^^.
作者回复: 是的。
delete从不删除值。delete只能删除引用,例如obj.x,或者with(obj) delete x,这些都是以引用的方式得到的,所以delete才能删除它们。
`delete 0`这种行为并不真的能够发生,它什么也没做,只是返回了true而已。
作者回复: “因为这是一个函数声明,也就是在编译的时候就创建了”
===
这个是你混乱的根源,因为从“函数声明”到“闭包”之间还有好几个环节的。
“函数创建的时候标识符x和y就被创建了”,这个函数内的x和y都是可以被parser到的,因此在函数静态解析完之后,函数就可以确定内部有x和y了,并且相应的静态作用域也(在语法处理相关的内核逻辑部分)被创建了。但是这些东西是用户代码完全不可见的,你不会知道,也用不到。
然后是一个函数作为表达式得到它的一个实例的过程,也就是
> (function () {}).xxxxx
中“.xxxx”之前的部分作为表达式被独立处理的时候,这种情况下函数会有一个自己的环境,该环境也是“基于前面得到的静态作用域”来创建的。这个环境仍然不为用户所知,只是它能传递,例如你可以把这样的东西“返回(return)”或“赋值(=)”给别的东西,你会发现这个东西的“环境/执行上下文”并没有变,所以它们在“返回(return)”或“赋值(=)”的过程之前就被创建了,并且能被这些操作所“传递”。
最后才是闭包,闭包只发生于“f()”的这个“调用操作()”之后——注意是“之后”,所以高程等等都是说它在“执行之后”标识符才会生成,而我这一章都是在讲“静态语义”,所以我会说他在函数(作为一个静态的对象)创建的时候就已经有这些标识符了。
这两者都不矛盾。根本上来说,函数实例、函数闭包,都不过是“静态词法解析结果(函数定义/(un)Anonymous Function Definition)”的一个映像,这个“函数定义”就是第4小节讲的那个东东。
Ok,既然“x,y都是代码执行前就被(静态分析)创建了的”,那么哪种情况下"x"才不是“词法的”呢?
function f() { let y; return x+y }
注意在这个例子中,"x"就不是词法的,它在任何情况下都不被创建,不在f()的标识符列表中,也不在语法/词法分析中。
Ok. 这是第1个问题。
关于第2个问题,答案其实如上所述:如果“创建”是指静态语义中的一个“函数”,那么确实是创建了的。——但它还没有“绑定”到一个闭包的执行上下文中。
关于第3个问题,是这样的,这一整个课程(系列)其实不只20讲,大概会是40~45讲的样子。但编辑同学不允许我公布后续内容的计划(呜……),所以……我只能告诉你,在下一个课程里面,才会讲到闭包。
作者回复: 动态语言的诸多细节,其实要到18讲之后才会讨论到。不过有个概念上的问题,并不是说有动态类型就是动态语言,或者说支持动态执行就是动态语言。有很多方面的“动态语言的特性”,这个需要详细解析。
仅是说初始化这一项,使用动态赋值的原因是因为这个值必须要到执行期环境中才能确定下来,而在静态语法分析阶段是确认不了的。——所以它不可能“先于环境”而执行。
作者回复: 是的。简洁,正确。^^.
作者回复: 是的。不过所有的6种声明都是如此,非独var声明。
作者回复: > 这种情况下使用var声明的变量名尽管也会添加到varNames列表,但它也可以从varNames中移除(这是唯一一种能从varNames中移除项的特例...
文章中是解释过的。可能我没有足够地强调这个特例,或者它的特殊性吧。
作者回复: 不着急。会慢慢讲到的。^^.
还有,我所讲的有些会与MDN有出入,这个你可以先存疑。不必急着得到“YES/NO”这样的答案,慢慢地听到后面就会知道我为什么这么讲了。
作者回复: 这个讲法倒也不错,比较容易理解。
不过有些不严谨的地方,比如说,“var x;”这样的声明,“声明阶段和初始化阶段一齐发生”是对的,因为var声明的时候,名字声明并初始化为undefined确实是同时发生的。但是,如果是“var x = 100;”,那么就比较容易混淆了。因为“x = 100”其实是赋值,而不是引擎层面的“初始化”。
对于let来说,“声明阶段和初始化阶段是分开”其实更不严谨。确切地说,let就没有“初始化阶段”。而用户代码“let x = 100”中的“x = 100”就是赋值阶段了。“let 没有初始化阶段”,所以才会出现它在未赋值之前不能读的现象。
用这样三个阶段来解释这件事情,跟ECMAScript中的逻辑并不矛盾(而且也可以正确解释),只是细节上需要严谨一点、留意一点就行。
作者回复: 你是对的。在JavaScript中,“全局环境”里面的var与let/const是用了两个东西来管理的,所以他们也确实是创建在不同的地方。
但是从“作用域链”的角度上来说,它们并没有级别高低(也就是parent没有相互指向)。使得它们存取的效果有差别的,是因为“全局环境”采用了词法环境优先(也就是let/const声明)的顺序。
作者回复: “var y = 100”不是表达式,而是语句。
所以“var x = y = 100”里面:
- “var x ...”是语句语法(Variable Statement)
- “= ...”是初始化器(Initializer,严格来说,也不是表达式,而是语法的一部分)
- “y = 100”是表达式(expression)。
所以你列的问题,报错的原因是“语句在语法上不支持这种写法”,即在“ = ...”中的“...”上面,必须是一个用来做“初始器”的表达式,而不能“再是一个语句”。
参见ECMAScript:
https://tc39.es/ecma262/#sec-variable-statement
https://tc39.es/ecma262/#prod-VariableDeclaration
作者回复: 有主题的哦~ ^^.
目录中对这一个段落的定义是“JavaScript语言是如何构建起来的”。基本上是讲静态语言语义,以及它在引擎级别上的实现。5个小节,从值、引用等基础,到模块和语句,基本上覆盖构成语言的核心组件。
可不是在乱讲哦。^^.
作者回复: 一是因为向下兼容,这是规范订制过程中面临的主要挑战之一。第二个原因,在于按照JavaScript的核心设计,在原理上这就很难绕过去。例如就包括不受兼容性影响的import,其实也是有变量提升效果的。例如下面这样的代码也是成立的:
```
console.log(x);
import x from './test.js';
```
作者回复: 是在加载.js文件之后,执行第一行用户代码之前,就已经完成了全部的词法分析(当然,除了eval执行的)。
作者回复: 是的。
作者回复: 这里没错的。不过因果是说反了,varNames并不“决定”列表中的变量不能用delete删除。但是由于delete global.x无效时,总是不会删除varNames中的项,所以表现起来,也是它里面的项不能删除。
其实严格来说,并没有其它地方的逻辑需要特别地使用到varNames,它非常没存在感。只是从引擎的角度上来说,如果没有varNames,它也不能在运行期识别哪个全局变量是从var声明来的,而哪个又是直接在global上创建的属性。
作者回复: 1. y是属性(也就是一个属性引用)。y是泄露到全局的变量,他事实上变成了global.y。
2. js能“变量名=值”这样赋值,这称为赋值表达式,但“var 变量名=值”是一个声明语句,其中的“=值”称为初始器,他只是跟赋值表达式“形似”而已,在语法和语义上都不是同一个东西。