但在我们开始之前,我们应当对一些事情十分清楚:从程序的角度看,语言的规则/文法(参见本丛书的 类型与文法)为语句的顺序决定了一个非常可预知、可靠的行为。所以我们将要讨论的是在你的JS程序中 应当永远观察不到的东西。
警告: 如果你曾经 观察到 过我们将要描述的编译器语句重排,那明显是违反了语言规范,而且无疑是那个JS引擎的Bug——它应当被报告并且修复!但是更常见的是你 怀疑 JS引擎里发生了什么疯狂的事,而事实上它只是你自己代码中的一个Bug(可能是一个“竞合状态”)——所以先检查那里,多检查几遍。在JS调试器使用断点并一行一行地步过你的代码,将是帮你在 你的代码 中找出这样的Bug的最强大的工具。
考虑下面的代码:
这段代码没有任何异步表达(除了早先讨论的罕见的异步I/O),所以最有可能的推测是它会一行一行地、从上到下地处理。
举个例子,引擎可能会发现如果实际上这样执行代码会更快:
a = 10;
a++;
b = 30;
b++;
console.log( a + b ); // 42
或者是这样:
或者甚至是:
// 因为`a`和`b`都不再被使用,我们可以内联而且根本不需要它们!
在所有这些情况下,JS引擎在它的编译期间进行着安全的优化,而最终的 可观察到 的结果将是相同的。
编译器重排会造成可观测的副作用(因此绝不会被允许)的其他例子,包括任何带有副作用的函数调用(特别是getter函数),或者ES6的Proxy对象(参见本丛书的 ES6与未来)。
考虑如下代码:
console.log( b );
return 1;
}
var a, b, c;
// ES5.1 getter 字面语法
c = {
get bar() {
}
};
a = 10;
b = 30;
a += foo(); // 30
b += c.bar; // 11
如果不是为了这个代码段中的console.log(..)
语句(只是作为这个例子中观察副作用的方便形式),JS引擎将会更加自由,如果它想(谁知道它想不想!?),它会重排这段代码:
多亏JS语义,我们不会观测到看起来很危险的编译器语句重排,但是理解源代码被编写的方式(从上到下)与它在编译后运行的方式之间的联系是多么微弱,依然是很重要的。