let x = x 扔出的错误乃右边的表达式求值时产生运行时错误,但是 let x 本身是有效的。
实际上如果不是在console里一句一句执行,还没等扔运行时错误(ReferenceError)第二个let x语句就首先产生了早期错误(SyntaxError)。
【补充】
@萧井陌 的答案提到 console 特殊环境,确实,这里与 console 的 repl 实现有一定的关系,下面 @余博伦 答案也指出 edge 的 console 下结果就不同。不过 edge 的问题其实是在 repl 下每次执行代码中的 let 定义只对当次的代码有效,即其每次执行实际上是在一个单独 scope 里。这是 edge devtools 的一个缺陷。
但是注意,这个例子并不是“不存在的粪坑”。
通常,let 所在的 block 一旦发生错误,你是没办法重新继续执行该 block 内 let 语句之后的语句的。你也许想用 try catch,但 try 自己就形成了一个 block,所以此路不通。
但是有一个例外,那就是浏览器里的 global script 代码,其 let 定义是跨 script 块有效的。(node.js 因为每个模块本质上是包装过的函数,所以是无法实现的。)并且一个 script 块报错后面的 script 块会继续执行。
代码如下:
<!doctype html> <script> function throws() { throw '!' } let x = throws() </script> <script> console.log(typeof x) </script> <script> let x </script>
这里我用 let x = throws() 代替了 let x = x,是因为这里只需要扔出运行异常,而不必是引用自身这种特例。
第一个script跑完之后,x 虽然已经存在了,但是处于未初始化的状态,即类似TDZ的情况。之后即使使用 typeof x 也会报错(通常未声明变量会返回 'undefined')。
chrome里报错如下:
test.html:3 Uncaught !
test.html:7 Uncaught ReferenceError: x is not defined
test.html:9 Uncaught SyntaxError: Identifier 'x' has already been declared
edge里报错如下:
!
Use before declaration
Let/Const redeclaration
(注意:edge貌似会并行parse不同的script块,因为第三条是early error,而第二条是运行时错误,所以有时候你会看到后两条报错的顺序是相反的。)
ff里报错如下:
uncaught exception: !
ReferenceError: can't access lexical declaration `x' before initialization test.html:7:13
SyntaxError: redeclaration of let x test.html:9:1
看起来,还是ff的错误信息一如既往的最清楚。
顺便,我发现其实这个问题之前已经有人提过了:js中用let声明变量出错时,变量依然被声明且无法再赋值是什么原理? 并且下面的答案也都比较准确的回答了,所以大家还是要善用搜索。