作者回复: Yes! 满分答案👍
作者回复: 是的。不过,这算是题解。中心还是模块装载执行和标识符绑定全过程来着😄
标识符和值绑定是“声明”语法处理的核心,而六种声明是js静态语法的核心。而静态语法,也就是这一整篇“语言如何构建”的核心了🤓
作者回复: 😃👍
作者回复: 对哒!赞+2
作者回复: Yes. 这样理解没错。
作者回复: 有点特殊,但就处理逻辑(以及目的)上来说,也并不算是特例。
其实“函数定义(Function Definition)”这个概念出现得比较奇怪。
仔细分析一下就明白了,你想,“函数声明(Function Declaration)”是静态语义的,它在执行期的结果是empty,所以它必须是具名的才能导出,因为“声明(6种)”的目的都是具名,而export原则上只能“导出一个名字”。所以,由于“函数定义(Function Definition)”没有名字,所以它不能按函数声明来处理。
然后,由于“函数表达式(Function Expression)”是动态语义的,有执行语义(也就是执行结果返回不是empty),得到一个运行期概念上的“闭包”。但这并不是最关键处,最关键的地方在于函数表达式没名字——即使是具名的函数表达式,它的名字也只能闭包内有影响。由于它没有名字一个可供导出的名字,所以也不能直接直接用作export的对象。
那么到底在概念上该怎么说这个东西呢?ECMAScript在这里就加了这么一层概念,叫“函数声明(Function Declaration)”,一方面它是有静态语义的,它声明了某个东西;另一方面,它的名字又是迟绑定的,需要到了执行期根据“name = FunctionExpression”中的`name`来确认。
在这种情况下,其实“函数定义(Function Definition)”就是“函数表达式”的一层概念封装:它又有在外层(或被关联的对象)中的名字,它又是表达式;它的执行结果又是闭包,又是实例。
所以箭头函数看起来是特例,但用在导出语法的“这个位置”时,概念上却仍然是“封装了一层的‘箭头函数表达式’”,仍然还是“函数定义”。
作者回复: 这是因为类似于:
obj = {
f: function() {
},
...
}
这样位置中的匿名函数,在ECMAScript中都是称为“匿名函数定义”,而不是“匿名函数表达式”。所有在语法上记为“x = functionExpression”的,在处理上都与一般表达式有不同,这是一个非常非常小的细节,但在引擎层面,加入了好大一段逻辑呢。
真正的匿名函数表达式,是下面这样的:
> (1 + function() {})
就是:把它直接用在一个表达式计算过程中,而不是把它用来赋值(或绑定,或引用)给另一个东西。这种情况下,它才是按匿名函数表达式来处理的。
这几讲都是讲JavaScript的静态语言特性的,所以“词法分析以及对应的引擎处理”是要点,在词法分析阶段,关键在于“不能为它(函数、函数表达式、函数定义等等)创建闭包”。因为在静态处理阶段,还没有“闭包”这个概念,所以好多东西处理起来跟我们平常的理解不同,这就是根由了。
作者回复: 正好,刚写完“Y”同学的留言,你不妨看看,应该正好能回答你的疑问。
(万恶的极客时间没有提供分留言链接的功能,产品同学要打手板心5次 🤔)
作者回复: 多谢。我晚些请编辑处理一下~ ^^.
作者回复: 多谢指出。我已经反馈给编辑同学了~
作者回复: 关于这个问题,其实还挺好玩儿的,因为它涉及到`execute_context.VariableEnvironment`这个东西怎么用的问题。
首先,其实词法环境(LexicalEnvironment)与变量环境(VariableEnvironment)并没有一个所谓优先级的问题。在实现上,它们之间是一个使用env.outer来衔接起来的链,所以所谓查找顺序,本质上就是二者谁在链的外层的问题。——然而,从实际实现的角度上,二者并不需要强调谁在外层,这种关系不是必须的(它们只需要衔接在一起就可以了)。
除了在函数或全局初始化需要一个表来指示“哪些东西是var和函数名”之外,事实上区分var/let/const之间的必要性是不大的。并且即使是在这种情况下,引擎也并不需要VariableEnvironment这个东东的参与,因为在它们初始化时,引擎是可以访问来自源代码的ParserNode的。也就是说,它可以直接访问原始的信息,而不必依赖VariableEnvironment这个列表。
VariableEnvironment这个东西,以及LexicalEnvironment,它们都是给运行期的上下文用的,也只在运行期才有意义。——更进一步的,只有对全局和函数,在它们的执行期才有意义(对函数来说,是它被调用的时候)。
为什么呢?就目前而言,VariableEnvironment其实只在一种情况下被用到。——就是当全局或函数内出现eval('var x...')这样的代码的时候。因为只有在这种情况下,在相应的变量环境中,才会需要执行上下文去访问变量环境列表,并动态地向中间插入一个新的名字。由于事实上var变量只能全局和函数有用,所以四种执行上下文(Global/Function/Module/Eval)中,虽然都有这两个成员,但其实Module.VariableEnvironment是没有用的,而Eval.VariableEnvironment受限于是否是在严格模式(当处在非严格模式时,它指向外层的——例如函数的VariableEnvironment;当处在严格模式时,它将自己创建一个,以隔离开对外部环境的影响)。
所以,本质上你来看VariableEnvironment这个东西的时候,不是要去“检查”它有什么样的优先级,而是直接看到“它有什么用,它怎么用”。再一次强调,对于单向链表访问来说,所谓“优先级”就是谁在链尾的问题;但即使如此,它对VariableEnvironment的使用来说也没有什么意义,因为VariableEnvironment归ExecuteContext使用,而ExecuteContext根本不care这个顺序。
作者回复: 他这个写法是错误的。细节上有不少错误~ :(
ECMAScript确实比较难读(是真的难),我也许今后会出一个专题来讲这个……但这个真不保证呵,因为想了解这个的人太少太少了。
关于你的第一个问题,你打开ECMAScript规范,查找一下VariableEnvironment这个关键词,看一下它在哪些地方用的,怎么用的,就可以明白了。——之所以这么讲,是因为它只有24处,很少,通读一下也没什么,反倒有利于你熟悉文档。^^.
作者回复: 是的。这个“执行期”在用户代码之前。
作者回复: ```
// t.mjs
console.log("here =>", typeof f);
import f from './f.mjs';
// f.mjs
export default function() {}
console.log('NOW');
// test
> node --experimental-modules t.mjs
NOW
here => function
```
想想,
1. 为什么`here`为什么是function呢?import语句还没有到呢。
2. 为什么`NOW`在`here`之前?这是哪个时候的执行过程?
作者回复: 不知道你的引擎的情况,我这里显示会是default。
```
// f.mjs
export default function() {}
// t.mjs
import f from './f.mjs';
console.log(f.name);
// test
> node --experimental-modules t.mjs
default
```
作者回复: 是的。这样没问题。除了缺一咪咪的严谨之外,你的理解是对的。^^.
作者回复: 这个问题请参考一下在评论区给Marvin的回复就好了。^^.
作者回复: 第一个也不是“(具名)函数表达式”。因为如果它是表达式(expression),那么它的执行结果就是一个实例,而实例是无法导出的。只有它是一个“函数定义”,它才能在“静态语法分析之后、代码执行之前”被导出。
作者回复: 这个细述起来有点复杂,因为有好些细节的地方都能被称为“顶层的(代码)”。简而言之,你把一个.js文件中所有在全局的声明语句去掉,剩下的就是顶层代码了。
顶层这个概念其实挺重要的,因为tc39现在有一份提案在推,就是top-level await。这个在引擎实现的层面上,以及应用的层面上都挺关键的。
作者回复: 呵呵,其实真讲JavaScript的地方并不特别的多。就好象讲数据结构总需要一门基础语言一样,那个基础语言其实不重要,重要的是数据结构。
除了简单的语法和一些惯例之外,这门课真在讲“JavaScipt怎么用”的地方并不多。^^.