關(guān)于
本文由 [WowBar][WowBar] 團(tuán)隊(duì)首發(fā)于 [GitHub][GitHub]
作者: yvongyang
-
目錄
-
語(yǔ)句和表達(dá)式
JavaScript 中表達(dá)式和語(yǔ)句的主要區(qū)別在于一條語(yǔ)句執(zhí)行一個(gè)動(dòng)作,一個(gè)表達(dá)式產(chǎn)生一個(gè)值。意思是一個(gè)表達(dá)式執(zhí)行后一定會(huì)生成一個(gè)值,而語(yǔ)句不一定會(huì)產(chǎn)生值。語(yǔ)句主要是用來執(zhí)行動(dòng)作,程序就是由一系列語(yǔ)句組成。
例如:// 表達(dá)式 name 1 + x getNames() // 語(yǔ)句 var name = 'yang'; function getNames() {} var foo = getNames() {};接下來的內(nèi)容里不會(huì)介紹表達(dá)式,只是列出來表達(dá)式的分類,語(yǔ)句部分會(huì)分別介紹語(yǔ)句的用法和示例,如果對(duì)于表達(dá)式和語(yǔ)句的內(nèi)容比較清楚的可以直接跳到本章最后一部分——表達(dá)式和語(yǔ)句的比較。
-
表達(dá)式
表達(dá)式分為基本的表達(dá)式(包括基本關(guān)鍵字),還有左值表達(dá)式以及運(yùn)算符。
1. 基本表達(dá)式
- this關(guān)鍵字
- 字面量(null,布爾值字面量,數(shù)字字面量,字符串字面量)
- 初始化字面量(數(shù)組字面量[],對(duì)象字面量{},正則表達(dá)式字面量
/ab+c/i) - 函數(shù)表達(dá)式
- 類表達(dá)式
- 分組操作符
() - 模板字面量 `..${...}..`
2. 左值表達(dá)式
- 屬性訪問符
- new
- 元屬性:new.target
- super
- 函數(shù)調(diào)用
- 參數(shù)列表(arguments, ...arguments)
- import
3. 運(yùn)算符
- 一元
delete, void, typeof, +, -, ~, ! - 算術(shù)
+, -, /, *, %, A++, A--, ++A, --A - 比較
<, >, <=, >=, in, instanceof,==,===,!==,!=== - 條件
condition ? ifTrue : ifFalse - 賦值
=, -=, +=, *=, /=, &=, |=, 解構(gòu)賦值如[a, b] = [1, 2]、{a, b} = {a: 1, b: 2}等 - 逗號(hào)
, - 位移,二進(jìn)制,二元邏輯
<<, >>, >>>; &, ^, |; &&, ||等
-
語(yǔ)句
語(yǔ)句分為聲明語(yǔ)句、流程控制語(yǔ)句和其他語(yǔ)句。
其中,流程控制語(yǔ)句分為基本流程控制語(yǔ)句、迭代語(yǔ)句、跳轉(zhuǎn)語(yǔ)句和條件語(yǔ)句。具體如下。1. 聲明語(yǔ)句
-
1.1 變量聲明
1.1.1 var 聲明
聲明一個(gè)變量,并可以地將其初始化為一個(gè)值
var a; var a = 2; var a = 2, b = 3; // 多個(gè)變量的初始化var 聲明的變量是可以提升的,提升意味著無論變量實(shí)際在哪里聲明的,都會(huì)被當(dāng)成在當(dāng)前作用域頂部聲明的變量??聪旅媸纠?4。根據(jù)示例 1,2,3 顯示的情況,建議始終聲明變量,無論它們?cè)诤瘮?shù)還是全局作用域內(nèi)。
var 聲明的函數(shù)表達(dá)式不能提升。對(duì)于未聲明的變量,可以用
typeof檢測(cè)其是否存在且不會(huì)報(bào)錯(cuò)。// 示例 1: 聲明的變量的作用域在其聲明位置的上下文中,而未聲明變量是全局的; // 建議始終聲明變量,無論它們是否在函數(shù)還是全局作用域內(nèi) function x() { y = 1; // 在嚴(yán)格模式(strict mode)下會(huì)拋出 ReferenceError 異常 var z = 2; } x(); console.log(y); // 打印 "1" console.log(z); // 拋出 ReferenceError: z 未在 x 外部聲明 // 示例 2:聲明的變量在任何代碼執(zhí)行前創(chuàng)建(會(huì)被提升),未聲明變量只有在執(zhí)行賦值操作時(shí)被創(chuàng)建; console.log(a); // 拋出 ReferenceError。 console.log('still going...'); // 永不執(zhí)行。 console.log(a); // 打印 "undefined" 或 ""(不同瀏覽器實(shí)現(xiàn)不同)。 var a; console.log('still going...'); // 打印 "still going..."。 // 示例 3:聲明的變量是它所在上下文環(huán)境的不可配置屬性,非聲明變量是可配置的(如可被刪除) var a = 1; b = 2; delete this.a; // 在嚴(yán)格模式(strict mode)下拋出TypeError,其他情況下執(zhí)行失敗并無任何提示。 delete this.b; console.log(a, b); // 拋出ReferenceError。 // 'b'屬性已經(jīng)被刪除。 // 示例 4: 變量提升 var x = y, y = 'A'; console.log(x + y); // undefinedA // 實(shí)際會(huì)被轉(zhuǎn)換為: var x; var y; x = y; y = 'A';1.1.2 let 聲明
聲明一個(gè)塊級(jí)作用域的變量,并可以將其初始化。
let x; let x = 1; let x = 1, y = 2;與 var 關(guān)鍵字聲明變量的不同點(diǎn)在于:
-
var聲明的變量只能是全局或者整個(gè)函數(shù)塊的,let/const聲明的變量只在其聲明的塊或子塊中使用;(示例 1) -
let/const不會(huì)在全局聲明時(shí)創(chuàng)建window對(duì)象的屬性,而 var 會(huì)。(示例 2) -
let/const在同一個(gè)塊作用域或函數(shù)中不能重復(fù)聲明(會(huì)報(bào)錯(cuò)),var可以;(示例 3,4) -
var聲明的變量會(huì)被初始化為undefined,let/const聲明的變量直到它們的定義被執(zhí)行時(shí)才會(huì)初始化。量會(huì)被初始化為undefined,let/const聲明的變量直到它們的定義被執(zhí)行時(shí)才會(huì)初始化。
// 示例 1 function varTest() { var x = 1; { var x = 2; // 同樣的變量! console.log(x); // 2 } console.log(x); // 2 } function letTest() { let x = 1; { let x = 2; // 不同的變量 console.log(x); // 2 } console.log(x); // 1 } // 示例 2 var x = 'global'; let y = 'global'; console.log(this.x); // "global" console.log(this.y); // undefined // 示例 3 if (x) { let foo; let foo; // SyntaxError thrown. } // 示例 4:case 沒用 `{}` 包裹起來沒形成塊作用域,所以兩個(gè) `foo` 會(huì)在同一個(gè)塊中被聲明,所以報(bào)錯(cuò)。 let x = 1; switch(x) { case 0: let foo; break; case 1: let foo; // SyntaxError for redeclaration. break; } // 示例 5 function do_something() { console.log(bar); // undefined console.log(typeof foo); // ReferenceError,typeof也不安全 var bar = 1; let foo = 2; } // 示例 6 function go(n) { // n here is defined! console.log(n); // Object {a: [1,2,3]} for (let n of n.a) { // ReferenceError console.log(n); } } go({a: [1, 2, 3]});1.1.3 const
聲明一個(gè)塊作用域中的變量,并必須初始化一個(gè)值。與 let 用法基本相同,除了聲明的變量的值不能被改變。
let a = 1; a = 2; console.log(a); // 2 const c = 1; c = 2; // Uncaught SyntaxError: Invalid or unexpected token -
-
1.2 函數(shù)聲明
每個(gè)函數(shù)都是一個(gè) Function 對(duì)象,與其他對(duì)象的區(qū)別在于可被調(diào)用;
若函數(shù)沒有 return 語(yǔ)句,則返回 undefined;
函數(shù)是值傳遞方式(對(duì)象是引用傳遞);
Es6 開始,嚴(yán)格模式下,塊里的函數(shù)作用域?yàn)檫@個(gè)塊。非嚴(yán)格模式下的塊級(jí)函數(shù)不要用。
-
定義函數(shù)的方式有3種:
函數(shù)聲明: 普通函數(shù)聲明,生成器函數(shù)聲明
構(gòu)造函數(shù): 普通的構(gòu)造函數(shù) Function, 生成器構(gòu)造函數(shù) GeneratorFunction。(不推薦構(gòu)造函數(shù)的方式定義函數(shù),函數(shù)體為字符串,會(huì)引起其他問題)
函數(shù)表達(dá)式: 函數(shù)表達(dá)式,函數(shù)生成器表達(dá)式,箭頭函數(shù)表達(dá)式
寫法示例:
// 函數(shù)聲明定義函數(shù)
function getName(name1, name2, ...) {
// 語(yǔ)句
}
// 構(gòu)造函數(shù)定義函數(shù)
var getName = new Function('name1', 'name2', 'return "myName:" + name1');
getName('yang'); // "myName:yang"
// 函數(shù)表達(dá)式定義函數(shù)
var getName = function(name1, name2) {
return 'myName:' + name1;
}
// 函數(shù)表達(dá)式
(function bar() {})
函數(shù)聲明和表達(dá)式區(qū)別:
1. 最主要的區(qū)別在于函數(shù)表達(dá)式可以省略函數(shù)名稱,就是創(chuàng)建匿名函數(shù);
2. 函數(shù)表達(dá)式未省略函數(shù)名稱,函數(shù)名只能在函數(shù)體內(nèi)用,函數(shù)聲明的函數(shù)名可以在其作用域內(nèi)被使用;
2. 函數(shù)聲明可以提升,函數(shù)表達(dá)式不可以提升,所以表達(dá)式不能在調(diào)用之前使用;
3. 函數(shù)表達(dá)式可被用作 IIFE(即時(shí)調(diào)用的函數(shù)表達(dá)式)。
var y = function x() {};
alert(x); // throws an error
// IIFE: 函數(shù)只使用一次時(shí)調(diào)用
(function() {
// 語(yǔ)句
})();
函數(shù)表達(dá)式 name 屬性:
被函數(shù)表達(dá)式賦值的變量有 name 屬性,如果把這個(gè)變量賦值給另一個(gè)變量,name 屬性值也不會(huì)改變。
// 匿名函數(shù):name屬性的值就是被賦值的變量的名稱(隱藏值)
var func = () => {}
// func.name
// "func"
// 非匿名函數(shù):那name屬性的值就是這個(gè)函數(shù)的名稱(顯性值)
var funb = function haha() {}
// funb.name
// "haha"
var fund = func;
// fund.name
// "func"
1.2.1 function
// 不同引擎中最大的傳參數(shù)量不同 function name(param1, param2, ...) { // 語(yǔ)句 }
1.2.2 函數(shù)生成器聲明 function*
定義一個(gè)生成器函數(shù),返回一個(gè) Generator 對(duì)象。
Generator 對(duì)象:由 generator function 返回的對(duì)象,符合可迭代協(xié)議和迭代器協(xié)議。
function *gen() {
// 語(yǔ)句
yield 10;
x = yield 'foo';
yield x;
}
生成器函數(shù)在執(zhí)行時(shí)能暫停,后面又能從暫停處繼續(xù)執(zhí)行;
調(diào)用一個(gè)生成器函數(shù)并不能馬上執(zhí)行它里面的語(yǔ)句,而是返回一個(gè)這個(gè)生成器的迭代器對(duì)象;
當(dāng)?shù)鞯?next() 方法被調(diào)用時(shí),其內(nèi)的語(yǔ)句會(huì)執(zhí)行到第一個(gè)后續(xù)出現(xiàn) yield 的位置為止,yield 后面緊跟迭代器要返回的值。
next() 返回一個(gè)對(duì)象,包含兩個(gè)屬性:value 和 done,value 表示本次 yield 表達(dá)式的返回值,done 為布爾類型,表示生成器是否已經(jīng)執(zhí)行完畢并返回。
若在生成器函數(shù)中調(diào)用 return 語(yǔ)句時(shí),會(huì)導(dǎo)致生成器立即變?yōu)橥瓿蔂顟B(tài),即調(diào)用 next() 方法返回的對(duì)象的 done 為 true,return 后面的值會(huì)作為當(dāng)前調(diào)用 next() 返回的 value 值。
function* yieldAndReturn() {
yield "Y";
return "R";//顯式返回處,可以觀察到 done 也立即變?yōu)榱?true
yield "unreachable";// 不會(huì)被執(zhí)行了
}
var gen = yieldAndReturn()
console.log(gen.next()); // { value: "Y", done: false }
console.log(gen.next()); // { value: "R", done: true }
console.log(gen.next()); // { value: undefined, done: true }
yield* 表示將執(zhí)行權(quán)移交給另一個(gè)生成器函數(shù)(當(dāng)前生成器暫停執(zhí)行),調(diào)用 next() 方法時(shí),如果傳入了參數(shù),那么這個(gè)參數(shù)會(huì)傳給上一條執(zhí)行的 yield 語(yǔ)句左邊的變量:
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i){
yield i;
yield* anotherGenerator(i);// 移交執(zhí)行權(quán)
yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
生成器函數(shù)不能當(dāng)作構(gòu)造器使用,否則會(huì)報(bào)錯(cuò)。
function*表達(dá)式 與 function*聲明 有相似的語(yǔ)法,唯一區(qū)別在于 function*表達(dá)式 可以省略函數(shù)名。
var x = function*(y) {
yield y * y;
};
1.2.3 async function
定義一個(gè)返回 AsyncFunction 對(duì)象的異步函數(shù)。
異步函數(shù)指通過事件循環(huán)異步執(zhí)行的函數(shù),會(huì)通過一個(gè)隱式的 Promise 返回結(jié)果。
Js 中每個(gè)異步函數(shù)都是 AsyncFunction 對(duì)象,該對(duì)象不是全局對(duì)象,需要用Object.getPrototypeOf(async function(){}).constructor獲取async function name(param1, param2, ...) { // 語(yǔ)句 }
可以包含 await 指令,await 會(huì)暫停異步函數(shù)的執(zhí)行,并等待 Promise 執(zhí)行,然后繼續(xù)執(zhí)行異步函數(shù),并返回結(jié)果。
await 只能在異步函數(shù)中使用,否則會(huì)報(bào)錯(cuò)。
async/await 是為了簡(jiǎn)化使用多個(gè) Promise 時(shí)的行為,就像是結(jié)合了 generators 和 promises。
使用 async 函數(shù)重寫 promise 鏈:
// Promise
function getProcessedData(url) {
return downloadData(url) // 返回一個(gè) promise 對(duì)象
.catch(e => {
return downloadFallbackData(url) // 返回一個(gè) promise 對(duì)象
})
.then(v => {
return processDataInWorker(v); // 返回一個(gè) promise 對(duì)象
});
}
// Async:return 時(shí),async function 的返回值將被隱式地傳遞給 Promise.resolve。
async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch (e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}
-
1.3 類聲明
ES6 中的類跟其他語(yǔ)言中的類類似,是基于原型繼承的。不過 ES6 中的類是基于已有自定義類型的語(yǔ)法糖,typeof 檢測(cè)類可以發(fā)現(xiàn)為 function。
// 簡(jiǎn)單的類聲明 class PersonClass { constructor(name) { this.name = name; } sayName() { console.log(this.name); } } // 自定義類型實(shí)現(xiàn)上述代碼 function PersonType(name) { this.name = name; } PersonType.prototype.sayName = function() { console.log(this.name); }上述例子可以看出,類中的構(gòu)造函數(shù)實(shí)際相當(dāng)于自定義類型的 PersonType 函數(shù),類中的 sayName 方法是構(gòu)造函數(shù)原型上的方法。
定義類的兩種形式:類聲明 和 類表達(dá)式。
類聲明和類表達(dá)式的代碼都是強(qiáng)制嚴(yán)格模式的。
和函數(shù)表達(dá)式一樣,類表達(dá)式也可以省略類名。如果不省略類名,則類表達(dá)式中的類名只能在類體內(nèi)部使用。后續(xù)會(huì)單獨(dú)講講類。
2. 流程語(yǔ)句
-
2.1 基本語(yǔ)句
2.1.1 塊語(yǔ)句
組合0或多個(gè)語(yǔ)句, 可以與label一起用。
{語(yǔ)句組合} 或 標(biāo)簽標(biāo)識(shí)符: {語(yǔ)句組合}塊語(yǔ)句示例:
示例 2 不會(huì)報(bào)錯(cuò),因?yàn)閴K級(jí)作用域的存在,并且輸出的是 1。// 示例 1 var a = 1; { var a = 2; } console.log(a); // Output:2 // 示例 2 const a = 1; { const a = 2; } console,log(a); // Output:1 // 示例 3 label: { const a = 1; }塊語(yǔ)句返回值示例:
塊返回的值為塊中最后一條語(yǔ)句的返回值,不過因?yàn)檎Z(yǔ)句的值獲取不到,所以了解即可。var a; function b() {return 'yang';} try { throw 'haha'; } catch(e) { } // Output: undefined var a; function b() {return 'yang';} // Output: ? b() {return 'yang';}2.1.2 空語(yǔ)句
不會(huì)執(zhí)行任何語(yǔ)句
;空語(yǔ)句示例:
// 跟 for 循環(huán)一起的空語(yǔ)句(空語(yǔ)句最好寫注釋以防混淆) for (let i = 0; i < 5; i++) /* Empty statement */; // if語(yǔ)句 if (one); // do nothing else if (two); // do nothing else all(); -
2.2 迭代語(yǔ)句
2.2.1 while/do...while
while (condition) statement // 想執(zhí)行多行語(yǔ)句可用塊語(yǔ)句 do statement // 想執(zhí)行多行語(yǔ)句可用塊語(yǔ)句 while (condition);while可在某個(gè)condition(條件表達(dá)式)值為真的前提下,執(zhí)行循環(huán)直到表達(dá)式值為false;do...while執(zhí)行指定語(yǔ)句的循環(huán)直到condition(條件表達(dá)式)值為 false,與while語(yǔ)句區(qū)別在于在執(zhí)行statement后檢測(cè)condition,所以statement至少執(zhí)行一次。兩者差別示例:
var i = 1; do { console.log('do..while', i); i++; } while (i < 1); // 輸出: // "do...while" // 1 var j = 1; while (j < 1) { console.log('while', j); j++; } // 沒有輸出2.2.2 for/for...of/for...in/for await...of
1.for: 創(chuàng)建循環(huán),含三個(gè)可選的表達(dá)式,表達(dá)式包圍在圓括號(hào)中并由分號(hào)分割,后跟一個(gè)在循環(huán)中執(zhí)行的語(yǔ)句(通常是一個(gè)塊語(yǔ)句,即用
{}包裹起來的語(yǔ)句)。// initialization 為一個(gè)表達(dá)式(包含賦值表達(dá)式)或者變量聲明,若沒有任何語(yǔ)句要執(zhí)行,則使用空語(yǔ)句 `(;)` for ([initialization]; [condition]; [final-expression]) statement2.for...of: 循環(huán)遍歷可迭代對(duì)象(Array, Map, Set, String, TypedArray, arguments 對(duì)象等)要迭代的值。
for (variable of iterable) { //statements }3.for...in: 以任意順序迭代對(duì)象的可枚舉屬性。(除 Symbol 以外)
for (variable in object) statement4.for await...of: 在異步或同步可迭代對(duì)象上創(chuàng)建一個(gè)迭代循環(huán),為每個(gè)不同屬性的值執(zhí)行語(yǔ)句。
for await (variable of iterable) statementfor :
如果省略了中間可選的條件表達(dá)式(condition塊),則必須確保在循環(huán)體內(nèi)跳出(break 語(yǔ)句),不然會(huì)陷入死循環(huán)。
如果省略所有表達(dá)式,則確保跳出循環(huán)并且修改增量,使break語(yǔ)句在某條件下為 true.(見示例 2)// 示例 1 var arr = []; for (var i = 0; i < 9; i++) { arr.push(function() { console.log(i); }); } console.log(arr.forEach(item => console.log(item()))); // Output: // [9, 9, 9, 9, 9, 9, 9, 9, 9] // 示例 2 var i = 0; for (;;) { if (i > 3) break; console.log(i); i++; } // 示例 3 for (var i = 0; i < 9; i++); console.log(i); // 9for...of:
// 迭代 Array let iterable = [10, 20, 30]; for (let value of iterable) { value += 1; console.log(value); } // Output: // 11 // 21 // 31 // 迭代 String let iterable = 'boo'; for (let value of iterable) { console.log(value); } // b // o // o // 迭代 Map let iterable = new Map([['a', 1], ['b', 2]]); for (let [key, value] of iterable) { console.log(value); } // 1 // 2for...in:
for...in 循環(huán)只遍歷可枚舉屬性,不可枚舉屬性不會(huì)遍歷,例如 String 的 indexOf() 方法,或者 Object.toString() 方法。
通常,在迭代過程中最好不要在對(duì)象上進(jìn)行添加、修改或者刪除屬性的操作,因?yàn)椴荒鼙WC這些被修改的屬性能被訪問到。Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; let iterable = [3, 5, 7]; iterable.foo = 'hello'; for (let i in iterable) { console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom" } for (let i in iterable) { if (iterable.hasOwnProperty(i)) { console.log(i); // logs 0, 1, 2, "foo" } } for (let i of iterable) { console.log(i); // logs 3, 5, 7 }注意:
- for...in 訪問 Array 時(shí)不一定會(huì)按次序訪問元素,這是依賴執(zhí)行環(huán)境的,而且訪問的是數(shù)組的索引,除了索引外還包括其他的屬性以及繼承的屬性;
- for...of 語(yǔ)句遍歷可迭代對(duì)象要迭代的數(shù)據(jù),所以用它來遍歷 Array 里的值更好;
- for...in 遍歷可枚舉屬性時(shí),若只考慮對(duì)象自身屬性,不包含原型,可以用
getOwnPropertyNames()或者hasOwnProperty()來確定是否是對(duì)象自身屬性。
for await...of:
異步生成器(或迭代異步可迭代對(duì)象)已經(jīng)實(shí)現(xiàn)了異步迭代器協(xié)議,用for await...of循環(huán):async function* asyncGenerator() { var i = 0; while (i < 3) { yield i++; } } (async function() { for await (num of asyncGenerator()) { console.log(num); } })(); // 0 // 1 // 2 -
2.3 條件語(yǔ)句
2.3.1 if
條件判斷
if (condition) statement1 [else if (condition) statement2] [else statement3] //中括號(hào)表示可選2.3.2 switch
評(píng)估一個(gè)表達(dá)式,若表達(dá)式的值與 case 子句匹配則執(zhí)行 case 子句相關(guān)聯(lián)的語(yǔ)句。
switch (expression) { case value1: // 當(dāng) expression 的結(jié)果與 value1 匹配時(shí),執(zhí)行此處語(yǔ)句 [break;] ... [default: // 如果 expression 與上面的 value 值都不匹配,執(zhí)行此處語(yǔ)句 [break;]] // 中括號(hào)表示可選 -
2.4 跳轉(zhuǎn)語(yǔ)句
2.4.1 break 語(yǔ)句
中止當(dāng)前循環(huán)(或 switch 語(yǔ)句 或 label 語(yǔ)句),直接執(zhí)行被中止語(yǔ)句后面的語(yǔ)句。
break [label];
label (可選)—標(biāo)簽相關(guān)標(biāo)識(shí)符,如果 break 語(yǔ)句不在一個(gè)循環(huán)或 switch 語(yǔ)句中,則該項(xiàng)是必須的。// 示例 1: 循環(huán)中的 break 語(yǔ)句 var i = 0; while (i < 6) { i += 1; if (i == 3) break; console.log(i); } // Output: // 1 // 2 // 示例 2: break 語(yǔ)句和被標(biāo)記的塊語(yǔ)句 outer_block: { inner_block: { console.log('1'); break outer_block; } console.log ('haha') //被跳過 }2.4.2 continue 語(yǔ)句
終止執(zhí)行當(dāng)前(或標(biāo)簽)循環(huán)的語(yǔ)句,直接執(zhí)行下一個(gè)迭代循環(huán)。
continue [label];與
break語(yǔ)句的區(qū)別是,continue并不會(huì)終止循環(huán)的迭代:
在 while 循環(huán)中,控制流跳轉(zhuǎn)回條件判斷;
在 for 循環(huán)中,控制流跳轉(zhuǎn)到更新語(yǔ)句。// 示例 1: 循環(huán)中的 continue 語(yǔ)句 var i = 0; while (i < 6) { i += 1; if (i == 3) continue; console.log(i); } // Output: // 1 // 2 // 4 // 5 // 6 // 示例 2 var a = 0; var b = 8; checkAB: while(...) { checkB: while(...) { continue checkB; //每次都跳到 checkB 開始執(zhí)行 } }2.4.3 throw 語(yǔ)句
拋出一個(gè)用戶自定義的異常。當(dāng)前函數(shù)的執(zhí)行將被停止(throw之后的語(yǔ)句將不會(huì)執(zhí)行),并且控制將被傳遞到調(diào)用堆棧中的第一個(gè)catch塊。如果調(diào)用函數(shù)中沒有catch塊,程序?qū)?huì)終止。
throw expression;throw "Error"; // 拋出了一個(gè)值為字符串的異常 throw 42; // 拋出了一個(gè)值為整數(shù)42的異常 throw true; // 拋出了一個(gè)值為true的異常2.4.4 try...catch 語(yǔ)句
標(biāo)記要嘗試的語(yǔ)句塊,并指定一個(gè)出現(xiàn)異常時(shí)拋出的響應(yīng)。
try { try_statements } [catch (exception_var_1) {}] [catch (exception_var_2) {}] // exception_var_1, exception_var_2 保存 throw 語(yǔ)句指定的值(如 catch(e) 中的 e ), 可以用這個(gè)標(biāo)識(shí)符獲取拋出的異常信息,只在 catch 子句內(nèi)部使用。 [finally {}] // 在 try 塊和 catch 塊之后執(zhí)行,在下一個(gè) try 聲明之前執(zhí)行,無論是否有異常拋出總是執(zhí)行-
可以嵌套一個(gè)或更多的 try 語(yǔ)句,如果內(nèi)部的 try 語(yǔ)句沒有 catch 子句,就會(huì)進(jìn)入包裹它的 try 語(yǔ)句的 catch 子句。
try { try { throw new Error("oops"); } catch (ex) { console.error("inner", ex.message); } finally { console.log("finally"); } } catch (ex) { console.error("outer", ex.message); } // Output: // "inner" "oops" // "finally" // "outer" "oops"- finally 塊返回一個(gè)值,無論 try 和 catch 塊中是否有任何 return 語(yǔ)句,此值都將成為整個(gè) try-catch-finally 的返回值。
// try-catch 中的 return 必須是作為函數(shù)的返回值才行,不然會(huì)報(bào)錯(cuò)(見下面 return 語(yǔ)句)。此中情況下 try-catch 要放在函數(shù)中運(yùn)行。 (function() { try { try { throw new Error('oops'); } catch (ex) { console.error('inner', ex.message); throw ex; } finally { console.log('finally'); return; } } // 因?yàn)樵?finally 中 return,所以 `oops` 不會(huì)拋到外層 catch (ex) { console.error('outer', ex.message); } })(); // Output: // inner oops // finally // undefined // 整個(gè)函數(shù)的返回值
-
2.4.5 return 語(yǔ)句
終止函數(shù)的執(zhí)行,并返回一個(gè)指定的值給函數(shù)的調(diào)用者。
return [[expression]]
返回表達(dá)式的值,如果忽略表達(dá)式的值,則會(huì)返回undefined.
在 return 關(guān)鍵字和被返回的表達(dá)式之間若使用行終止符(回車換行符,行分隔符和段分隔符)則會(huì)自動(dòng)分號(hào)插入,如:
return
a + b;
// 會(huì)被自動(dòng)轉(zhuǎn)換為
return;
a + b;
var a = 1;
var b = 2;
(function() {
return
a + b;
})() // undefined
// 會(huì)被自動(dòng)轉(zhuǎn)換為
(function() {
return a + b;
})() // 3
也可以返回函數(shù)表達(dá)式,就是高階函數(shù)的定義,高階函數(shù)是一個(gè)接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為輸出返回的函數(shù)。
3. 其他語(yǔ)句
3.1 debugger
在程序中調(diào)用可用的調(diào)試功能,如設(shè)置斷點(diǎn)。
debugger
3.2 導(dǎo)入/導(dǎo)出:export、import
export: 從模塊中導(dǎo)出函數(shù),對(duì)象或原始值,以便其他程序可以通過import語(yǔ)句使用。
導(dǎo)出模式分為兩種:命名導(dǎo)出 和 默認(rèn)導(dǎo)出??梢栽诿恳粋€(gè)模塊中定義多個(gè)命名導(dǎo)出,但是只允許一個(gè)默認(rèn)導(dǎo)出。
導(dǎo)入/導(dǎo)出的模塊都是運(yùn)行在嚴(yán)格模式下。
導(dǎo)出示例:
export let name; // 導(dǎo)出單個(gè)屬性
export const myName = 'yang'; // 導(dǎo)出常量
export class ClassName {} // 導(dǎo)出類
export default defaultName; // 導(dǎo)出默認(rèn)屬性
export {name1, name2...} // 導(dǎo)出列表
export {defaultName as default, name1 as Wang...} // 重命名導(dǎo)出,將 name1 作為默認(rèn)屬性導(dǎo)出, name2 重命名為 Wang
// 模塊重定向,導(dǎo)入指定路徑的模塊并導(dǎo)出
export * from ...; // 導(dǎo)出指定模塊所有導(dǎo)出的屬性,除了默認(rèn)導(dǎo)出值
export {default} from ...; // 導(dǎo)出指定模塊中的默認(rèn)導(dǎo)出值
export {name1, name2...} from ...; // 導(dǎo)出指定模塊中某些屬性
export {import1 as name1, import2 as name2...} from ...; // 重命名導(dǎo)出指定模塊中某些屬性
import: 導(dǎo)入由另一個(gè)模塊導(dǎo)出的綁定。
瀏覽器中,import 語(yǔ)句只能在聲明了 type="module" 的 script 標(biāo)簽中使用。
還有一個(gè)類似函數(shù)動(dòng)態(tài)的 import(),不需要依賴 type="module" 的 script 標(biāo)簽。
靜態(tài) import 更容易從代碼靜態(tài)分析工具和 tree shaking 中受益,動(dòng)態(tài) import() 則在按需加載模塊時(shí)有用。
導(dǎo)入示例:
import * as names from 'export.js'; // 導(dǎo)入整個(gè)模塊內(nèi)容,使用 names 模塊名稱作為命名空間
import {myName, ClassName} from 'export.js'; // 導(dǎo)入多個(gè)接口
import name from 'export.js'; // 導(dǎo)入默認(rèn)接口(即用 export default 導(dǎo)出的接口)
import defaultName, {name1, newName as name2} from 'export.js'; // 同時(shí)導(dǎo)入默認(rèn)接口和多個(gè)其他接口,并重命名其中某些接口
import defaultName, * as names from 'export.js'; // 同時(shí)導(dǎo)入默認(rèn)接口和多個(gè)其他接口,其他接口全部導(dǎo)入并重命名為 names
import 'export.js'; // 導(dǎo)入的模塊作為副作用導(dǎo)入(只運(yùn)行模塊中的全局代碼),不導(dǎo)入模塊中的任何接口。
var promises = import('export.js'); // 可以像調(diào)用函數(shù)一樣來動(dòng)態(tài)的導(dǎo)入模塊。以這種方式調(diào)用,將返回一個(gè) promise。
promises.then((module) => {})
3.3 label
在語(yǔ)句前加個(gè)可以引用的標(biāo)識(shí)符,可以和 break 或 continue 語(yǔ)句一起用。
label: statement
// 標(biāo)記塊,并使用 break
foo: {
console.log('face');
break foo;
console.log('this will not be executed');
}
console.log('swap');
// for 循環(huán)中使用標(biāo)記
var str = "";
loop1:
for (var i = 0; i < 5; i++) {
if (i === 1) {
continue loop1;
}
str = str + i;
}
console.log(str); // '0234'
目前在非嚴(yán)格模式下,可以對(duì)函數(shù)聲明進(jìn)行標(biāo)記,但是嚴(yán)格模式下不可以。生成器函數(shù)不論在什么模式下都不能被標(biāo)記。
L: function F() {}
'use strict';
L: function F() {}
// VM170:2 Uncaught SyntaxError: In strict mode code, functions can only be declared at top level or inside a block.
L: function* F() {}
// VM175:1 Uncaught SyntaxError: Generators can only be declared at the top level or inside a block.
3.4 with
with 語(yǔ)句(不推薦,了解即可),用于擴(kuò)展語(yǔ)句的作用域鏈。
在 ECMAScript 5 嚴(yán)格模式中該標(biāo)簽已被禁止。推薦的替代方案是聲明一個(gè)臨時(shí)變量來承載你所需要的屬性。with (expression) { statement }
示例:
var a, x, y;
var r = 10;
var Math = {};
with (Math) {
a = PI * r * r;
x = r * cos(PI);
y = r * sin(PI / 2);
}
// Uncaught ReferenceError: PI is not defined
// 因?yàn)樽饔糜蛑写嬖?Math 變量,所以先查找該變量中的 Math 對(duì)象是否有 PI 屬性,發(fā)現(xiàn)沒有所以報(bào)錯(cuò)。
// 'with' 語(yǔ)句將變量 Math 對(duì)象添加到作用域鏈的頂端,在查找變量值 PI 時(shí),會(huì)在指定的對(duì)象 Math 中查找,發(fā)現(xiàn)沒有所以報(bào)錯(cuò)。with 查找對(duì)象時(shí),會(huì)先從當(dāng)前作用域中查找,所以查找起來將會(huì)很慢。而且調(diào)試起來也會(huì)麻煩。
-
表達(dá)式語(yǔ)句
任何表達(dá)式都可以成為語(yǔ)句,就是說在任何需要寫語(yǔ)句的地方,都可以寫表達(dá)式,這樣的語(yǔ)句叫做表達(dá)式語(yǔ)句,表達(dá)式語(yǔ)句是一種特殊的語(yǔ)句。反過來,我們不能在寫表達(dá)式的地方寫語(yǔ)句。
下圖是 ecma262 規(guī)范中 If 語(yǔ)句的語(yǔ)法。其中有 Statement 的地方都可以使用 Expression 即表達(dá)式,例如下方示例中的 callback 函數(shù)調(diào)用表達(dá)式,就是替代了原來的語(yǔ)句,也是表達(dá)式語(yǔ)句。
ECMAScript 2020 If 語(yǔ)句語(yǔ)法
示例:
// callback 為表達(dá)式語(yǔ)句,是一種特殊的語(yǔ)句 if (true) callback() -
比較
1. 如何區(qū)分表達(dá)式和語(yǔ)句呢?
1.看是否產(chǎn)生值判斷,對(duì)表達(dá)式求值一定會(huì)返回值,對(duì)語(yǔ)句求值未可能有返回值也可能沒有返回值;
2.看后面是否有分號(hào),有分號(hào)的一定是語(yǔ)句,沒有分號(hào)的可能是表達(dá)式也可能是語(yǔ)句。下面兩個(gè)例子,第一個(gè)能成功 log 的原因在于 if 語(yǔ)句括號(hào)里應(yīng)該為表達(dá)式,而 true 是表達(dá)式中的布爾值字面量。第二個(gè)
var a = 0是聲明語(yǔ)句而不是表達(dá)式,沒有返回值,所以會(huì)報(bào)錯(cuò)。if (true) { console.log('Hi'); } // 輸出: // Hi if (var a = 0) { console.log('Hi'); } // 輸出: // Uncaught SyntaxError: Unexpected token 'var'2. 相似的表達(dá)式和語(yǔ)句
2.1 if 語(yǔ)句和條件表達(dá)式
if 語(yǔ)句和條件表達(dá)式表示的含義一樣,只是一個(gè)是語(yǔ)句,一個(gè)是表達(dá)式會(huì)返回值而已。
var x; var y = -1; // if 語(yǔ)句 if (y >= 0) { x = y; } else { x = -y; } // 條件表達(dá)式 x = (y >= 0 ? y : -y); // 括號(hào)不是必須的,加上括號(hào)更容易閱讀2.2 函數(shù)聲明和函數(shù)表達(dá)式
函數(shù)表達(dá)式與函數(shù)聲明擁有幾乎相同的語(yǔ)法,但是有以下區(qū)別:
- 在函數(shù)表達(dá)式中可以省略函數(shù)名稱,省略函數(shù)名稱即為匿名函數(shù);函數(shù)聲明中則不能省略函數(shù)名;
- 函數(shù)表達(dá)式可以用作 IIFE (即時(shí)調(diào)用函數(shù)表達(dá)式),函數(shù)聲明不能用作 IIFE。
// 函數(shù)表達(dá)式:省略函數(shù)名 function () {} // 函數(shù)表達(dá)式:未省略函數(shù)名 // 寫法與函數(shù)聲明完全一致 function foo() {}未省略函數(shù)名的函數(shù)表達(dá)式與函數(shù)聲明沒有區(qū)別,但是作用不同:函數(shù)表達(dá)式產(chǎn)生值,即函數(shù);函數(shù)聲明導(dǎo)致動(dòng)作,創(chuàng)建一個(gè)變量,其值為函數(shù)。
未省略函數(shù)名的函數(shù)表達(dá)式中的函數(shù)名只能在函數(shù)內(nèi)部自調(diào)用,在函數(shù)外部調(diào)用會(huì)報(bào)錯(cuò)。示例:
var outSideFuncName = function inSideFuncName(x) { return x <= 1 ? 1 : x * inSideFuncName(x - 1); } outSideFuncName(5); // Output: 120 > outSideFuncName // Output: // ? inSideFuncName(x) { // return x <= 1 ? 1 : x * inSideFuncName(x - 1); // } > insideFuncName // Output: // Uncaught ReferenceError: inSideFuncName is not defined2.3 對(duì)象字面量表達(dá)式和塊語(yǔ)句
我們知道對(duì)象字面量是表達(dá)式,它的寫法為
{key: value}形式,塊語(yǔ)句是用{}包裹的語(yǔ)句。當(dāng)塊里包含的是label語(yǔ)句且label語(yǔ)句后面是表達(dá)式語(yǔ)句的時(shí)候,對(duì)象字面量表達(dá)式和塊語(yǔ)句的寫法可能會(huì)完全一致。
示例:{ foo: bar(3, 5) }上面的例子中既是對(duì)象字面量,又可以說是塊語(yǔ)句。作為塊語(yǔ)句而言,塊里則是標(biāo)簽為 foo 的 label 語(yǔ)句,標(biāo)簽后的語(yǔ)句為一個(gè)函數(shù)調(diào)用表達(dá)式,根據(jù)前面對(duì)表達(dá)式語(yǔ)句的定義, 可以知道
bar(3, 5)是一個(gè)表達(dá)式語(yǔ)句。所以在程序中我們看到的
{}有可能是字面量,有可能是塊語(yǔ)句,根據(jù)上下文情況區(qū)分??聪旅娴睦樱?/p>> [] + {} "[object Object]" > {} + [] 0為什么兩個(gè)結(jié)果不一致呢?原因就在于前面的
{}被作為字面量計(jì)算的,后面的是作為塊語(yǔ)句計(jì)算。例中還涉及到隱式轉(zhuǎn)換的問題,此處埋個(gè)伏筆,后續(xù)我會(huì)單獨(dú)出一篇文章講解。2.4 表達(dá)式中的逗號(hào)和語(yǔ)句中的分號(hào)
在 JavaScript 中,語(yǔ)句是用分號(hào)隔離,例如
foo(); bar();表達(dá)式可用逗號(hào)隔離,例如foo(), bar(),兩個(gè)表達(dá)式都會(huì)執(zhí)行,只是會(huì)返回后面的表達(dá)式的值。> "a", "b" 'b' > var x = ("a", "b"); > x 'b'3. 使用對(duì)象字面量和函數(shù)表達(dá)式作為語(yǔ)句
我們已經(jīng)知道了表達(dá)式可以放在任何需要語(yǔ)句的地方,這種即表達(dá)式語(yǔ)句。對(duì)于某些表達(dá)式與語(yǔ)句沒有區(qū)別的情況,如 2.3 中的對(duì)象字面量和塊,2.4 中的函數(shù)表達(dá)式和函數(shù)聲明,如何區(qū)分是表達(dá)式還是語(yǔ)句呢?一般情況下,是根據(jù)出現(xiàn)在表達(dá)式上下文還是語(yǔ)句上下文中區(qū)分,但是有一種情況例外,就是表達(dá)式語(yǔ)句。
因?yàn)楸磉_(dá)式語(yǔ)句是一種特殊的語(yǔ)句,所以其上下文為語(yǔ)句上下文,這種情況中的
{}會(huì)被當(dāng)作塊處理,function開頭的語(yǔ)法會(huì)被當(dāng)作函數(shù)聲明。為了避免歧義,JavaScript語(yǔ)法禁止表達(dá)式語(yǔ)句使用{和function開頭。如果一定要用這兩個(gè)開頭的表達(dá)式,并且讓它們僅作為表達(dá)式處理,則可以將表達(dá)式放到()分組操作符中,使它們出現(xiàn)在表達(dá)式的上下文中,并且不會(huì)改變表達(dá)式結(jié)果。(Expression)——分組操作符,由圓括號(hào)包裹表達(dá)式和子表達(dá)式,返回執(zhí)行表達(dá)式的結(jié)果。還有另一種方式確保在表達(dá)式上下文中解析表達(dá)式,就是使用一元操作符,如
!或+等。但是與()的不同點(diǎn)在于一元操作符會(huì)改變表達(dá)式的結(jié)果。見下方立即調(diào)用函數(shù)表達(dá)式中的示例。下面看看
eval和立即調(diào)用函數(shù)表達(dá)式中的應(yīng)用。-
eval
eval 是在語(yǔ)句上下文中解析其參數(shù),所以例子中上面的示例將
{}解析為塊,所以里面為 label 語(yǔ)句,因而輸出為123。 加上括號(hào)后,{}及其里面的內(nèi)容的上下文為表達(dá)式,所以{foo: 123}被視為字面量表達(dá)式,因而輸出為{foo: 123}。> eval("{foo: 123}"); 123 > eval("({ foo: 123 })") {foo: 123}-
立即調(diào)用函數(shù)表達(dá)式(IIFEs)
// 立即調(diào)用函數(shù)表達(dá)式 > (function () { return "hello" }()) 'hello' > (function () { return "hello" })() 'hello' // 省略括號(hào)之后的立即調(diào)用函數(shù)表達(dá)式 > function () { return "hello" }() Uncaught SyntaxError: Function statements require a function name > +function () {console.log('hello')}() hello NaN // 返回 NaN 是因?yàn)楸磉_(dá)式返回的為 undefined,所以 +undefined 為NaN > void function () {console.log('hello')}() hello undefined // 同理,void undefined 返回 undefined有一點(diǎn)要注意的是,連續(xù)使用 IIFEs 時(shí),要記得加分號(hào),否則會(huì)報(bào)錯(cuò)。因?yàn)楹竺娴?IIFE 會(huì)把前面的 IIFE 的結(jié)果當(dāng)作函數(shù)調(diào)用。
若想省略分號(hào),可以將一元運(yùn)算符放在立即調(diào)用函數(shù)表達(dá)式前面,因?yàn)闀?huì)有自動(dòng)分號(hào)插入。自動(dòng)分號(hào)插入機(jī)制后續(xù)我也會(huì)講到,此處埋上第二個(gè)伏筆。(function () {}()) (function () {}()) // VM613:1 Uncaught TypeError: (intermediate value)(...) is not a function (function () {}()); (function () {}()) // undefined void function () {}() void function () {}() // undefined -
參考
https://2ality.com/2012/09/expressions-vs-statements.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators
