語(yǔ)法(grammar)
本篇文章中主要探討 JavaScript 中一些容易讓人產(chǎn)生困惑、誤解的語(yǔ)法讲仰。
1. 語(yǔ)句(statement)和表達(dá)式(expression)
在計(jì)算機(jī)語(yǔ)言中慕趴,執(zhí)行特定任務(wù)的一組單詞痪蝇、數(shù)字和運(yùn)算符被稱為語(yǔ)句。如下:
a = b * 2;
語(yǔ)句由一個(gè)或多個(gè)表達(dá)式組成冕房。表達(dá)式是對(duì)一個(gè)變量或值的引用躏啰,或者是一組值和變量與運(yùn)算符的組合,可以返回一個(gè)結(jié)果值耙册。
舉例來(lái)說(shuō)给僵,a = b * 2;
這個(gè)語(yǔ)句中有四個(gè)表達(dá)式。
- 2 是一個(gè)字面值表達(dá)式详拙。
- b 是一個(gè)變量表達(dá)式帝际,表示獲取它的當(dāng)前值。
- b * 2 是一個(gè)算術(shù)表達(dá)式饶辙,表示進(jìn)行乘法運(yùn)算蹲诀。
- a = b * 2 是一個(gè)賦值表達(dá)式,意思是將表達(dá)式 b * 2 的結(jié)果賦值給變量 a弃揽。
1.1 語(yǔ)句的結(jié)果值
語(yǔ)句也會(huì)有一個(gè)結(jié)果值脯爪,獲取語(yǔ)句結(jié)果值的最直接辦法就是在控制臺(tái)輸入語(yǔ)句,默認(rèn)情況下控制臺(tái)會(huì)打印出最后一條語(yǔ)句的結(jié)果值矿微。
// 該表達(dá)式的結(jié)果值是 undefined痕慢,因?yàn)?var 的結(jié)果值是 undefined
var a = 3 * 6;
在代碼中,是無(wú)法獲取結(jié)果值的涌矢,語(yǔ)法暫時(shí)不允許將獲得語(yǔ)句的結(jié)果值賦值給變量:
// 該語(yǔ)句的結(jié)果值是 42掖举,即最后一個(gè)語(yǔ)句 b = 4 + 38 的結(jié)果值
var b;
if (true) {
b = 4 + 38;
}
不過(guò)可以通過(guò) eval
得到結(jié)果值(開發(fā)中避免使用):
var a, b;
a = eval("if (true) { b = 4 + 38; }");
a; // 42
1.2 表達(dá)式的副作用
大部分表達(dá)式?jīng)]有副作用,副作用會(huì)將變量的值改變娜庇。比如遞增運(yùn)算符++塔次,執(zhí)行完成后會(huì),變量本身會(huì)被改變思灌。
如下表達(dá)式會(huì)報(bào)錯(cuò)俺叭,根據(jù)優(yōu)先級(jí)首先執(zhí)行 a++,返回 42泰偿,然后執(zhí)行 ++42,這時(shí)候就會(huì)報(bào)錯(cuò)蜈垮,因?yàn)?++ 無(wú)法直接 在 42 這樣的值上產(chǎn)生副作用耗跛。
++a++; // ReferenceError
2. 自動(dòng)分號(hào)
有時(shí) JavaScript 會(huì)自動(dòng)為代碼行補(bǔ)上缺失的分號(hào),即自動(dòng)分號(hào)插入(Automatic Semicolon Insertion攒发,ASI)调塌。
注意: ASI 只在換行符處起作用,而不會(huì)在代碼行的中間插入分號(hào)惠猿。
如果 JavaScript 解析器發(fā)現(xiàn)代碼行可能因?yàn)槿笔Х痔?hào)而導(dǎo)致錯(cuò)誤羔砾,那么它就會(huì)自動(dòng)補(bǔ)上分號(hào)。并且,只有在代碼行末尾與換行符之間
除了空格和注釋之外沒有別的內(nèi)容時(shí)姜凄,它才會(huì)這樣做政溃。
如下代碼,會(huì)在 b 之后補(bǔ)上分號(hào):
var a = 42, b
c;
另外及 ASI 的情況是 break态秧、continue董虱、return 和 yield(ES6)等關(guān)鍵字。
ASI 是一個(gè)語(yǔ)法糾錯(cuò)機(jī)制申鱼。不要為了追求“代碼的美觀”愤诱,省去一些鍵盤輸入。所以在所有需要的地方加上分號(hào)捐友,將對(duì) ASI 的依賴降到最低淫半。
3. try...finally
finally 中的代碼總是會(huì)在 try 之后執(zhí)行,如果有 catch 的話則在 catch 之后執(zhí)行匣砖。
3.1 try 中有 return 語(yǔ)句
如下 return 先執(zhí)行科吭,將 foo 的返回值設(shè)置為 42,當(dāng) try 執(zhí)行完畢脆粥,接著執(zhí)行 finally砌溺,最后函數(shù)執(zhí)行完畢。
function foo() {
try {
return 42;
} finally {
console.log("Hello");
}
console.log("never runs");
}
console.log(foo());
// Hello
// 42
3.2 try 中的 throw
規(guī)則同上变隔。
function foo() {
try {
throw 42;
} finally {
console.log("Hello");
}
console.log("never runs");
}
console.log(foo());
// Hello
// Uncaught Exception: 42
3.3 finally 中拋出異常
如果 finally 中拋出異常规伐,函數(shù)就會(huì)終止,try 中 return 設(shè)置的返回值也會(huì)被丟棄:
function foo() {
try {
return 42;
} finally {
throw "Oops!";
console.log("never runs");
}
}
console.log(foo()); // Uncaught Exception: Oops!
3.4 try 中的 continue 和 break
如下匣缘,continue 在每次循環(huán)之后猖闪,會(huì)在 i++ 執(zhí)行之前執(zhí)行 console.log(i)
for (var i = 0; i < 10; i++) {
try {
continue;
} finally {
console.log(i);
}
}
// 0 1 2 3 4 5 6 7 8 9
3.5 finally 中的 return
finally 中的 return 會(huì)覆蓋 try 和 catch 中 return 的返回值:
function foo() {
try {
return 42;
} finally {
// 沒有返回語(yǔ)句,所以沒有覆蓋
}
}
function bar() {
try {
return 42;
} finally {
// 覆蓋前面的 return 42
return;
}
}
function baz() {
try {
return 42;
} finally {
// 覆蓋前面的 return 42
return "Hello";
}
}
foo(); // 42
bar(); // undefined
baz(); // Hello
4. switch
case 表達(dá)式的匹配算法與 === 相同肌厨。