看一個如下的例子。在函數 foo 中,使用了一組 try 語句。在 try 中有 return 語句,finally 中的內容還會執行嗎?
function foo(){ try{ return 0; } catch(err) { } finally { console.log("a") } }
得到的結果是:a 0。
根據結果可以看出finally執行了,return語句也生效了。
雖然 return 執行了,但是函數並沒有立即返回,又執行了 finally 里面的內容,這樣的行為違背了很多人的直覺。
我們改一下上面的例子,在finally也加入return語句,如下:
function foo(){ try{ return 0; } catch(err) { } finally { return 1; } }
得到的結果是:1
通過實際執行,我們看到,finally 中的 return “覆蓋”了 try 中的 return。在一個函數中執行了兩次 return,這已經超出了很多人的常識,也是其它語言中不會出現的一種行為。
面對如此怪異的行為,我們當然可以把它作為一個孤立的知識去記憶,但是實際上,這背后有一套機制在運作。
這一機制的基礎正是 JavaScript 語句執行的完成狀態,我們用一個標准類型來表示:Completion Record(Completion Record 用於描述異常、跳出等語句執行過程)。
Completion Record 表示一個語句執行完之后的結果,它有三個字段:
- [[type]] 表示完成的類型,有 break continue return throw 和 normal 幾種類型;
- [[value]] 表示語句的返回值,如果語句沒有,則是 empty;
- [[target]] 表示語句的目標,通常是一個 JavaScript 標簽。
JavaScript 正是依靠語句的 Completion Record 類型,方才可以在語句的復雜嵌套結構中,實現各種控制。接下來我們要來了解一下 JavaScript 使用 Completion Record 類型,控制語句執行的過程。
語句的分類如下:
普通語句執行后,會得到 [[type]] 為 normal 的 Completion Record,JavaScript 引擎遇到這樣的 Completion Record,會繼續執行下一條語句。
這些語句中,只有表達式語句會產生 [[value]],當然,從引擎控制的角度,這個 value 並沒有什么用處。
如果你經常使用 chrome 自帶的調試工具,可以知道,輸入一個表達式,在控制台可以得到結果,但是在前面加上 var,就變成了 undefined。
Chrome 控制台顯示的正是語句的 Completion Record 的 [[value]]。
現在解釋下在finally也加入return語句后,為什么得到的結果為1?
因為 finally 中的內容必須保證執行,所以 try/catch 執行完畢,即使得到的結果是非 normal 型的完成記錄,也必須要執行 finally。
而當 finally 執行也得到了非 normal 記錄,則會使 finally 中的記錄作為整個 try 結構的結果。
帶標簽的語句的作用:與完成記錄類型中的 target 相配合,用於跳出多層循環。
實際上,任何 JavaScript 語句是可以加標簽的,在語句前加冒號即可:
firstStatement: var i = 1;
跳出循環的例子:
top: for (var i = 0; i < 3; i++){ for (var j = 0; j < 3; j++){ if (i === 1 && j === 1) break top; console.log('i=' + i + ', j=' + j); } } // i=0, j=0 // i=0, j=1 // i=0, j=2 // i=1, j=0