1.6 语句顺序
代码中语句的顺序和 JavaScript 引擎执行语句的顺序并不一定要一致。这个陈述可能看起来似乎会很奇怪,所以我们要简单解释一下。
但在此之前,以下这一点我们应该完全清楚:这门语言的规则和语法(参见本系列的《你不知道的 JavaScript(上卷)》的“作用域和闭包”部分)已经从程序的角度在语序方面规定了可预测和非常可靠的特性。所以,接下来我们要讨论的内容你应该无法在自己的 JavaScript 程序中观察到。
如果你观察到了类似于我们将要展示的编译器对语句的重排序,那么这很明显违反了规范,而这一定是由所使用的 JavaScript 引擎中的 bug 引起的——该 bug 应该被报告和修正!但是更可能的情况是,当你怀疑 JavaScript 引擎做了什么疯狂的事情时,实际上却是你自己代码中的 bug(可能是竞态条件)引起的。所以首先要检查自己的代码,并且要反复检查。通过使用断点和单步执行一行一行地遍历代码,JavaScript 调试器就是用来发现这样 bug 的最强大工具。
考虑:
var a, b; a = 10; b = 30; a = a + 1; b = b + 1; console.log( a + b ); // 42
这段代码中没有显式的异步(除了前面介绍过的很少见的异步 I/O !),所以很可能它的执行过程是从上到下一行行进行的。
但是,JavaScript 引擎在编译这段代码之后(是的,JavaScript 是需要编译的,参见本系列的《你不知道的 JavaScript(上卷)》的“作用域和闭包”部分!)可能会发现通过(安全地)重新安排这些语句的顺序有可能提高执行速度。重点是,只要这个重新排序是不可见的,一切都没问题。
比如,引擎可能会发现,其实这样执行会更快:
var a, b; a = 10; a++; b = 30; b++; console.log( a + b ); // 42
或者这样:
var a, b; a = 11; b = 31; console.log( a + b ); // 42
或者甚至这样:
// 因为a和b不会被再次使用 // 我们可以inline,从而完全不需要它们! console.log( 42 ); // 42
前面的所有情况中,JavaScript 引擎在编译期间执行的都是安全的优化,最后可见的结果都是一样的。
但是这里有一种场景,其中特定的优化是不安全的,因此也是不允许的(当然,不用说这其实也根本不能称为优化):
var a, b; a = 10; b = 30; // 我们需要a和b处于递增之前的状态! console.log( a * b ); // 300 a = a + 1; b = b + 1; console.log( a + b ); // 42
还有其他一些例子,其中编译器重新排序会产生可见的副作用(因此必须禁止),比如会产生副作用的函数调用(特别是 getter 函数),或 ES6 代理对象(参考本系列的《你不知道的 JavaScript(下卷)》的“ES6 & Beyond”部分)。
考虑:
function foo() { console.log( b ); return 1; } var a, b, c; // ES5.1 getter字面量语法 c = { get bar() { console.log( a ); return 1; } }; a = 10; b = 30; a += foo(); // 30 b += c.bar; // 11 console.log( a + b ); // 42
如果不是因为代码片段中的语句 console.log(..) (只是作为一种方便的形式说明可见的副作用),JavaScript 引擎如果愿意的话,本来可以自由地把代码重新排序如下:
// ... a = 10 + foo(); b = 30 + c.bar; // ...
尽管 JavaScript 语义让我们不会见到编译器语句重排序可能导致的噩梦,这是一种幸运,但是代码编写的方式(从上到下的模式)和编译后执行的方式之间的联系非常脆弱,理解这一点也非常重要。
编译器语句重排序几乎就是并发和交互的微型隐喻。作为一个一般性的概念,清楚这一点能够使你更好地理解异步 JavaScript 代码流问题。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论