关于ES6的块级作用域中定义函数的疑问

这是关于JS的怪异行为。

非严格模式

很久之前我在其它地方看见过类似下列的代码:

var a = 0;
{
  a = 1;
  function a() {}
  a = 2;
  console.log(a);
}
console.log(a);

这段代码在Chrome和Firefox下运行的结果是:

2
1

这段代码是反直觉的,说实话来研究JS的这种行为是没有多大意义的,但是知道为什么也是挺有趣的一件事。

其实上述代码执行结果是JS的实现和语言规范出现冲突导致的,经过我查询资料了解到(不一定准确),在ES5规范中,JS的块中严格不允许出现函数定义,但是浏览器在实现时却支持了这种写法,这就到导致在ES6有块级作用域时函数在其中的定义就会出很多奇怪的行为。
例如在上例代码在Safari中运行就会出现和恶Chrome不同的结果,不过我没有mac设备无法进行测试。

这段代码的关键字有:函数提升变量环境(VariableEnvironment),词法环境(LexicalEnvironment),所以我查询到下列资料:

得出一个结论,其实上列的代码实际上等同于:

var a1 = undefined;
{
  let a2 = function a() {};
  a2 = 1;
  a1 = a2;
  a2 = 2;
  console.log(a2);
}
console.log(a1);

其中a1和a2都是a,区分出来只是为了方便理解,在块级作用域中定义的函数首先会提升到块顶部,在这里就是a2,它实际上存在于词法环境;同时也会提升到整段代码的顶部,在这里是a1,它实际上存在于变量环境,这是预编译阶段完成的。
接着在执行阶段,当代码执行到函数定义位置时,会进行一个赋值操作,内部a2的值赋值到了外部a1
于是整个程序结束后输出的是21

严格模式

上述操作只是在非严格模式下进行,当在严格模式下运行是结果似乎就符合逻辑了:

"use strict";
var a = 0;
{
  a = 1;
  function a() {}
  a = 2;
  console.log(a);
}
console.log(a);

代码输出

2
0

var替换为let

把var替换为let,那么结果和严格模式下的结果相同:

let a = 0;
{
  a = 1;
  function a() {}
  a = 2;
  console.log(a);
}
console.log(a);

代码输出

2
0

可能是因为ES6是默认严格模式原因

语言规范

当然最合适的是去查询语言规范,这里我给出相关的链接: https://262.ecma-international.org/6.0/#sec-block-level-function-declarations-web-legacy-compatibility-semantics 这段语言规范中描述了与本文相关的一些内容,目前在中文互联网中我没有找到相关的翻译,希望有小伙伴可以完成这个第一。

上诉内容只是我个人的想法与推测,毕竟预编译这个过程是黑箱操作,我现在还没有这个实例去查询相关的实现。

0 评论