作者回复: +1 ^^.
作者回复: 多谢。我在后面的课程中尽量注意这个 ^^.
作者回复: “因为这是一个函数声明,也就是在编译的时候就创建了”
===
这个是你混乱的根源,因为从“函数声明”到“闭包”之间还有好几个环节的。
“函数创建的时候标识符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讲的样子。但编辑同学不允许我公布后续内容的计划(呜……),所以……我只能告诉你,在下一个课程里面,才会讲到闭包。
作者回复: 是的。
delete从不删除值。delete只能删除引用,例如obj.x,或者with(obj) delete x,这些都是以引用的方式得到的,所以delete才能删除它们。
`delete 0`这种行为并不真的能够发生,它什么也没做,只是返回了true而已。
作者回复: 是的。不过所有的6种声明都是如此,非独var声明。
作者回复: 动态语言的诸多细节,其实要到18讲之后才会讨论到。不过有个概念上的问题,并不是说有动态类型就是动态语言,或者说支持动态执行就是动态语言。有很多方面的“动态语言的特性”,这个需要详细解析。
仅是说初始化这一项,使用动态赋值的原因是因为这个值必须要到执行期环境中才能确定下来,而在静态语法分析阶段是确认不了的。——所以它不可能“先于环境”而执行。
作者回复: 是的。简洁,正确。^^.
作者回复: > 这种情况下使用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执行的)。
作者回复: 是的。
作者回复: 是这样的,node缺省情况下是把.js文件当成模块来加载的,它会为每一个模块(亦即是.js文件)包一层所谓的“模块封装器”。这个封装器是一个函数。所以,.js文件中的代码事实上是运行在函数中的。这样一来,`var a`就变成了声明函数内的局部变量,而不是在全局global上的。
在node的控制台里面输入的代码是作为全局环境下的代码来执行的,因此如果你的代码是在node控制台上逐行执行,就没有问题。另外,如果你使用-e参数来执行,那么也能得到这样的效果(这种情况下node不会添加模块封装器):
```
> node -e "$(cat test.js)" -p
{ value: 100,
writable: true,
enumerable: true,
configurable: false }
```
> NOTE: 模块封装器,参见:http://nodejs.cn/api/modules.html#modules_the_module_wrapper
作者回复: 是的。
其中的y是一个“向未声明变量赋值”导致的变量名创建(变量泄露)。这种变量与`var x`这种“var变量声明”的区别,就在于变量泄露名字可以删除。
作者回复: 这里说到的 varNames在绝大多数情况下是没用的,与你这里想讨论的东西关系也不大。
window在浏览器环境中等同于global。你在浏览器控制台上查看一下就明白了:
```
# 这个globalThis是新EMAScript规范中声明的,它等义于传统的global
> window === globalThis
true
# 用下面的代码可以获得“传统的global”,并比较之
> window === Function('return this')()
true
```
所以所谓window,就是global,相同的东西。那么window中取变量也就是查global这个对象的属性表,之前也都说过了,不再讲了。至于var/let/const之间的关系,在全局域(global scope)中,var声明在global的属性表中,并在varNames中有一个登记;let/const共享使用同一个称为词法环境(lexicallyEnv)的东西,所以它们不能同名。词法环境在后面会讲到。
因此从原理上来说,全局作用域中的global对象属性,与词法环境中的名字其实是可以重名的。——所以,var与let/const的重名限制,主要是来自于语法。
例如:
```
# 添加属性
> global.n = 100
# 看起来有了全局变量的样子(实际上没有在varNames中登记)
> n
100
# 现在可以声明let
> let n = 200
# 这是一个Let变量
> n
200
# 如果你使用var声明,则与let/const会有重名冲突了
> var x = 300
> let x = 400
SyntaxError: Identifier 'x' has already been declared
```