關(guān)于
本文由 [WowBar][WowBar] 團隊首發(fā)于 [GitHub][GitHub]
作者: yvongyang
-
目錄
-
語句和表達式
JavaScript 中表達式和語句的主要區(qū)別在于一條語句執(zhí)行一個動作赡突,一個表達式產(chǎn)生一個值。意思是一個表達式執(zhí)行后一定會生成一個值,而語句不一定會產(chǎn)生值。語句主要是用來執(zhí)行動作,程序就是由一系列語句組成章咧。
例如:// 表達式 name 1 + x getNames() // 語句 var name = 'yang'; function getNames() {} var foo = getNames() {};
接下來的內(nèi)容里不會介紹表達式,只是列出來表達式的分類,語句部分會分別介紹語句的用法和示例置尔,如果對于表達式和語句的內(nèi)容比較清楚的可以直接跳到本章最后一部分——表達式和語句的比較。
-
表達式
表達式分為基本的表達式(包括基本關(guān)鍵字)蛹批,還有左值表達式以及運算符撰洗。
1. 基本表達式
- this關(guān)鍵字
- 字面量(null,布爾值字面量腐芍,數(shù)字字面量差导,字符串字面量)
- 初始化字面量(數(shù)組字面量[],對象字面量{}猪勇,正則表達式字面量
/ab+c/i
) - 函數(shù)表達式
- 類表達式
- 分組操作符
()
- 模板字面量 `..${...}..`
2. 左值表達式
- 屬性訪問符
- new
- 元屬性:new.target
- super
- 函數(shù)調(diào)用
- 參數(shù)列表(arguments, ...arguments)
- import
3. 運算符
- 一元
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}等 - 逗號
, - 位移,二進制泣刹,二元邏輯
<<, >>, >>>; &, ^, |; &&, ||等
-
語句
語句分為聲明語句助析、流程控制語句和其他語句。
其中椅您,流程控制語句分為基本流程控制語句外冀、迭代語句、跳轉(zhuǎn)語句和條件語句掀泳。具體如下雪隧。1. 聲明語句
-
1.1 變量聲明
1.1.1 var 聲明
聲明一個變量,并可以地將其初始化為一個值
var a; var a = 2; var a = 2, b = 3; // 多個變量的初始化
var 聲明的變量是可以提升的员舵,提升意味著無論變量實際在哪里聲明的脑沿,都會被當(dāng)成在當(dāng)前作用域頂部聲明的變量÷砥В看下面示例 4庄拇。根據(jù)示例 1,2韭邓,3 顯示的情況措近,建議始終聲明變量溶弟,無論它們在函數(shù)還是全局作用域內(nèi)。
var 聲明的函數(shù)表達式不能提升熄诡。對于未聲明的變量可很,可以用
typeof
檢測其是否存在且不會報錯。// 示例 1: 聲明的變量的作用域在其聲明位置的上下文中凰浮,而未聲明變量是全局的我抠; // 建議始終聲明變量,無論它們是否在函數(shù)還是全局作用域內(nèi) function x() { y = 1; // 在嚴(yán)格模式(strict mode)下會拋出 ReferenceError 異常 var z = 2; } x(); console.log(y); // 打印 "1" console.log(z); // 拋出 ReferenceError: z 未在 x 外部聲明 // 示例 2:聲明的變量在任何代碼執(zhí)行前創(chuàng)建(會被提升)袜茧,未聲明變量只有在執(zhí)行賦值操作時被創(chuàng)建菜拓; console.log(a); // 拋出 ReferenceError。 console.log('still going...'); // 永不執(zhí)行笛厦。 console.log(a); // 打印 "undefined" 或 ""(不同瀏覽器實現(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 // 實際會被轉(zhuǎn)換為: var x; var y; x = y; y = 'A';
1.1.2 let 聲明
聲明一個塊級作用域的變量,并可以將其初始化梦湘。
let x; let x = 1; let x = 1, y = 2;
與 var 關(guān)鍵字聲明變量的不同點在于:
-
var
聲明的變量只能是全局或者整個函數(shù)塊的瞎颗,let/const
聲明的變量只在其聲明的塊或子塊中使用;(示例 1) -
let/const
不會在全局聲明時創(chuàng)建window
對象的屬性捌议,而 var 會哼拔。(示例 2) -
let/const
在同一個塊作用域或函數(shù)中不能重復(fù)聲明(會報錯),var
可以瓣颅;(示例 3倦逐,4) -
var
聲明的變量會被初始化為undefined
,let/const
聲明的變量直到它們的定義被執(zhí)行時才會初始化宫补。量會被初始化為undefined
僻孝,let/const
聲明的變量直到它們的定義被執(zhí)行時才會初始化。
// 示例 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 沒用 `{}` 包裹起來沒形成塊作用域守谓,所以兩個 `foo` 會在同一個塊中被聲明,所以報錯您单。 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
聲明一個塊作用域中的變量,并必須初始化一個值虐秦。與 let 用法基本相同平酿,除了聲明的變量的值不能被改變凤优。
let a = 1; a = 2; console.log(a); // 2 const c = 1; c = 2; // Uncaught SyntaxError: Invalid or unexpected token
-
-
1.2 函數(shù)聲明
每個函數(shù)都是一個 Function 對象蜈彼,與其他對象的區(qū)別在于可被調(diào)用筑辨;
若函數(shù)沒有 return 語句,則返回 undefined幸逆;
函數(shù)是值傳遞方式(對象是引用傳遞)棍辕;
Es6 開始,嚴(yán)格模式下还绘,塊里的函數(shù)作用域為這個塊楚昭。非嚴(yán)格模式下的塊級函數(shù)不要用。
-
定義函數(shù)的方式有3種:
函數(shù)聲明: 普通函數(shù)聲明拍顷,生成器函數(shù)聲明
構(gòu)造函數(shù): 普通的構(gòu)造函數(shù) Function, 生成器構(gòu)造函數(shù) GeneratorFunction抚太。(不推薦構(gòu)造函數(shù)的方式定義函數(shù),函數(shù)體為字符串昔案,會引起其他問題)
函數(shù)表達式: 函數(shù)表達式尿贫,函數(shù)生成器表達式,箭頭函數(shù)表達式
寫法示例:
// 函數(shù)聲明定義函數(shù)
function getName(name1, name2, ...) {
// 語句
}
// 構(gòu)造函數(shù)定義函數(shù)
var getName = new Function('name1', 'name2', 'return "myName:" + name1');
getName('yang'); // "myName:yang"
// 函數(shù)表達式定義函數(shù)
var getName = function(name1, name2) {
return 'myName:' + name1;
}
// 函數(shù)表達式
(function bar() {})
函數(shù)聲明和表達式區(qū)別:
1. 最主要的區(qū)別在于函數(shù)表達式可以省略函數(shù)名稱踏揣,就是創(chuàng)建匿名函數(shù)庆亡;
2. 函數(shù)表達式未省略函數(shù)名稱,函數(shù)名只能在函數(shù)體內(nèi)用呼伸,函數(shù)聲明的函數(shù)名可以在其作用域內(nèi)被使用身冀;
2. 函數(shù)聲明可以提升,函數(shù)表達式不可以提升括享,所以表達式不能在調(diào)用之前使用搂根;
3. 函數(shù)表達式可被用作 IIFE(即時調(diào)用的函數(shù)表達式)。
var y = function x() {};
alert(x); // throws an error
// IIFE: 函數(shù)只使用一次時調(diào)用
(function() {
// 語句
})();
函數(shù)表達式 name 屬性:
被函數(shù)表達式賦值的變量有 name 屬性铃辖,如果把這個變量賦值給另一個變量剩愧,name 屬性值也不會改變。
// 匿名函數(shù):name屬性的值就是被賦值的變量的名稱(隱藏值)
var func = () => {}
// func.name
// "func"
// 非匿名函數(shù):那name屬性的值就是這個函數(shù)的名稱(顯性值)
var funb = function haha() {}
// funb.name
// "haha"
var fund = func;
// fund.name
// "func"
1.2.1 function
// 不同引擎中最大的傳參數(shù)量不同 function name(param1, param2, ...) { // 語句 }
1.2.2 函數(shù)生成器聲明 function*
定義一個生成器函數(shù)娇斩,返回一個 Generator 對象仁卷。
Generator 對象:由 generator function 返回的對象,符合可迭代協(xié)議和迭代器協(xié)議犬第。
function *gen() {
// 語句
yield 10;
x = yield 'foo';
yield x;
}
生成器函數(shù)在執(zhí)行時能暫停锦积,后面又能從暫停處繼續(xù)執(zhí)行;
調(diào)用一個生成器函數(shù)并不能馬上執(zhí)行它里面的語句歉嗓,而是返回一個這個生成器的迭代器對象丰介;
當(dāng)?shù)鞯?next() 方法被調(diào)用時,其內(nèi)的語句會執(zhí)行到第一個后續(xù)出現(xiàn) yield 的位置為止,yield 后面緊跟迭代器要返回的值哮幢。
next() 返回一個對象带膀,包含兩個屬性:value 和 done,value 表示本次 yield 表達式的返回值橙垢,done 為布爾類型垛叨,表示生成器是否已經(jīng)執(zhí)行完畢并返回。
若在生成器函數(shù)中調(diào)用 return 語句時柜某,會導(dǎo)致生成器立即變?yōu)橥瓿蔂顟B(tài)嗽元,即調(diào)用 next() 方法返回的對象的 done 為 true,return 后面的值會作為當(dāng)前調(diào)用 next() 返回的 value 值莺琳。
function* yieldAndReturn() {
yield "Y";
return "R";//顯式返回處还棱,可以觀察到 done 也立即變?yōu)榱?true
yield "unreachable";// 不會被執(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)移交給另一個生成器函數(shù)(當(dāng)前生成器暫停執(zhí)行),調(diào)用 next() 方法時惭等,如果傳入了參數(shù)珍手,那么這個參數(shù)會傳給上一條執(zhí)行的 yield 語句左邊的變量:
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)造器使用,否則會報錯辞做。
function*表達式
與 function*聲明
有相似的語法琳要,唯一區(qū)別在于 function*表達式
可以省略函數(shù)名。
var x = function*(y) {
yield y * y;
};
1.2.3 async function
定義一個返回 AsyncFunction 對象的異步函數(shù)秤茅。
異步函數(shù)指通過事件循環(huán)異步執(zhí)行的函數(shù)稚补,會通過一個隱式的 Promise 返回結(jié)果。
Js 中每個異步函數(shù)都是 AsyncFunction 對象框喳,該對象不是全局對象课幕,需要用Object.getPrototypeOf(async function(){}).constructor
獲取async function name(param1, param2, ...) { // 語句 }
可以包含 await 指令,await 會暫停異步函數(shù)的執(zhí)行五垮,并等待 Promise 執(zhí)行乍惊,然后繼續(xù)執(zhí)行異步函數(shù),并返回結(jié)果放仗。
await 只能在異步函數(shù)中使用润绎,否則會報錯。
async/await 是為了簡化使用多個 Promise 時的行為诞挨,就像是結(jié)合了 generators 和 promises莉撇。
使用 async 函數(shù)重寫 promise 鏈:
// Promise
function getProcessedData(url) {
return downloadData(url) // 返回一個 promise 對象
.catch(e => {
return downloadFallbackData(url) // 返回一個 promise 對象
})
.then(v => {
return processDataInWorker(v); // 返回一個 promise 對象
});
}
// Async:return 時,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 中的類跟其他語言中的類類似棍郎,是基于原型繼承的。不過 ES6 中的類是基于已有自定義類型的語法糖银室,typeof 檢測類可以發(fā)現(xiàn)為 function涂佃。
// 簡單的類聲明 class PersonClass { constructor(name) { this.name = name; } sayName() { console.log(this.name); } } // 自定義類型實現(xiàn)上述代碼 function PersonType(name) { this.name = name; } PersonType.prototype.sayName = function() { console.log(this.name); }
上述例子可以看出静秆,類中的構(gòu)造函數(shù)實際相當(dāng)于自定義類型的 PersonType 函數(shù),類中的 sayName 方法是構(gòu)造函數(shù)原型上的方法巡李。
定義類的兩種形式:類聲明 和 類表達式。
類聲明和類表達式的代碼都是強制嚴(yán)格模式的扶认。
和函數(shù)表達式一樣侨拦,類表達式也可以省略類名。如果不省略類名辐宾,則類表達式中的類名只能在類體內(nèi)部使用狱从。后續(xù)會單獨講講類。
2. 流程語句
-
2.1 基本語句
2.1.1 塊語句
組合0或多個語句, 可以與label一起用叠纹。
{語句組合} 或 標(biāo)簽標(biāo)識符: {語句組合}
塊語句示例:
示例 2 不會報錯季研,因為塊級作用域的存在,并且輸出的是 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; }
塊語句返回值示例:
塊返回的值為塊中最后一條語句的返回值与涡,不過因為語句的值獲取不到,所以了解即可持偏。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 空語句
不會執(zhí)行任何語句
;
空語句示例:
// 跟 for 循環(huán)一起的空語句(空語句最好寫注釋以防混淆) for (let i = 0; i < 5; i++) /* Empty statement */; // if語句 if (one); // do nothing else if (two); // do nothing else all();
-
2.2 迭代語句
2.2.1 while/do...while
while (condition) statement // 想執(zhí)行多行語句可用塊語句 do statement // 想執(zhí)行多行語句可用塊語句 while (condition);
while
可在某個condition
(條件表達式)值為真的前提下驼卖,執(zhí)行循環(huán)直到表達式值為false;do...while
執(zhí)行指定語句的循環(huán)直到condition
(條件表達式)值為 false鸿秆,與while
語句區(qū)別在于在執(zhí)行statement
后檢測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)卿叽,含三個可選的表達式桥胞,表達式包圍在圓括號中并由分號分割,后跟一個在循環(huán)中執(zhí)行的語句(通常是一個塊語句考婴,即用
{}
包裹起來的語句)贩虾。// initialization 為一個表達式(包含賦值表達式)或者變量聲明,若沒有任何語句要執(zhí)行蕉扮,則使用空語句 `(;)` for ([initialization]; [condition]; [final-expression]) statement
2.for...of: 循環(huán)遍歷可迭代對象(Array, Map, Set, String, TypedArray, arguments 對象等)要迭代的值整胃。
for (variable of iterable) { //statements }
3.for...in: 以任意順序迭代對象的可枚舉屬性。(除 Symbol 以外)
for (variable in object) statement
4.for await...of: 在異步或同步可迭代對象上創(chuàng)建一個迭代循環(huán)喳钟,為每個不同屬性的值執(zhí)行語句屁使。
for await (variable of iterable) statement
for :
如果省略了中間可選的條件表達式(condition
塊),則必須確保在循環(huán)體內(nèi)跳出(break 語句
)奔则,不然會陷入死循環(huán)蛮寂。
如果省略所有表達式,則確保跳出循環(huán)并且修改增量易茬,使break語句
在某條件下為 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); // 9
for...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 // 2
for...in:
for...in 循環(huán)只遍歷可枚舉屬性酬蹋,不可枚舉屬性不會遍歷及老,例如 String 的 indexOf() 方法,或者 Object.toString() 方法范抓。
通常骄恶,在迭代過程中最好不要在對象上進行添加、修改或者刪除屬性的操作匕垫,因為不能保證這些被修改的屬性能被訪問到僧鲁。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 時不一定會按次序訪問元素,這是依賴執(zhí)行環(huán)境的象泵,而且訪問的是數(shù)組的索引寞秃,除了索引外還包括其他的屬性以及繼承的屬性;
- for...of 語句遍歷可迭代對象要迭代的數(shù)據(jù)偶惠,所以用它來遍歷 Array 里的值更好春寿;
- for...in 遍歷可枚舉屬性時,若只考慮對象自身屬性忽孽,不包含原型绑改,可以用
getOwnPropertyNames()
或者hasOwnProperty()
來確定是否是對象自身屬性。
for await...of:
異步生成器(或迭代異步可迭代對象)已經(jīng)實現(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 條件語句
2.3.1 if
條件判斷
if (condition) statement1 [else if (condition) statement2] [else statement3] //中括號表示可選
2.3.2 switch
評估一個表達式绢淀,若表達式的值與 case 子句匹配則執(zhí)行 case 子句相關(guān)聯(lián)的語句。
switch (expression) { case value1: // 當(dāng) expression 的結(jié)果與 value1 匹配時瘾腰,執(zhí)行此處語句 [break;] ... [default: // 如果 expression 與上面的 value 值都不匹配皆的,執(zhí)行此處語句 [break;]] // 中括號表示可選
-
2.4 跳轉(zhuǎn)語句
2.4.1 break 語句
中止當(dāng)前循環(huán)(或 switch 語句 或 label 語句),直接執(zhí)行被中止語句后面的語句蹋盆。
break [label];
label (可選)—標(biāo)簽相關(guān)標(biāo)識符费薄,如果 break 語句不在一個循環(huán)或 switch 語句中,則該項是必須的栖雾。// 示例 1: 循環(huán)中的 break 語句 var i = 0; while (i < 6) { i += 1; if (i == 3) break; console.log(i); } // Output: // 1 // 2 // 示例 2: break 語句和被標(biāo)記的塊語句 outer_block: { inner_block: { console.log('1'); break outer_block; } console.log ('haha') //被跳過 }
2.4.2 continue 語句
終止執(zhí)行當(dāng)前(或標(biāo)簽)循環(huán)的語句楞抡,直接執(zhí)行下一個迭代循環(huán)。
continue [label];
與
break
語句的區(qū)別是析藕,continue
并不會終止循環(huán)的迭代:
在 while 循環(huán)中召廷,控制流跳轉(zhuǎn)回條件判斷;
在 for 循環(huán)中账胧,控制流跳轉(zhuǎn)到更新語句竞慢。// 示例 1: 循環(huán)中的 continue 語句 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 語句
拋出一個用戶自定義的異常。當(dāng)前函數(shù)的執(zhí)行將被停止(throw之后的語句將不會執(zhí)行)治泥,并且控制將被傳遞到調(diào)用堆棧中的第一個catch塊筹煮。如果調(diào)用函數(shù)中沒有catch塊,程序?qū)K止居夹。
throw expression;
throw "Error"; // 拋出了一個值為字符串的異常 throw 42; // 拋出了一個值為整數(shù)42的異常 throw true; // 拋出了一個值為true的異常
2.4.4 try...catch 語句
標(biāo)記要嘗試的語句塊败潦,并指定一個出現(xiàn)異常時拋出的響應(yīng)本冲。
try { try_statements } [catch (exception_var_1) {}] [catch (exception_var_2) {}] // exception_var_1, exception_var_2 保存 throw 語句指定的值(如 catch(e) 中的 e ), 可以用這個標(biāo)識符獲取拋出的異常信息,只在 catch 子句內(nèi)部使用劫扒。 [finally {}] // 在 try 塊和 catch 塊之后執(zhí)行檬洞,在下一個 try 聲明之前執(zhí)行,無論是否有異常拋出總是執(zhí)行
-
可以嵌套一個或更多的 try 語句沟饥,如果內(nèi)部的 try 語句沒有 catch 子句疮胖,就會進入包裹它的 try 語句的 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 塊返回一個值闷板,無論 try 和 catch 塊中是否有任何 return 語句,此值都將成為整個 try-catch-finally 的返回值院塞。
// try-catch 中的 return 必須是作為函數(shù)的返回值才行遮晚,不然會報錯(見下面 return 語句)。此中情況下 try-catch 要放在函數(shù)中運行拦止。 (function() { try { try { throw new Error('oops'); } catch (ex) { console.error('inner', ex.message); throw ex; } finally { console.log('finally'); return; } } // 因為在 finally 中 return县遣,所以 `oops` 不會拋到外層 catch (ex) { console.error('outer', ex.message); } })(); // Output: // inner oops // finally // undefined // 整個函數(shù)的返回值
-
2.4.5 return 語句
終止函數(shù)的執(zhí)行,并返回一個指定的值給函數(shù)的調(diào)用者汹族。
return [[expression]]
返回表達式的值萧求,如果忽略表達式的值,則會返回undefined.
在 return 關(guān)鍵字和被返回的表達式之間若使用行終止符(回車換行符顶瞒,行分隔符和段分隔符)則會自動分號插入夸政,如:
return
a + b;
// 會被自動轉(zhuǎn)換為
return;
a + b;
var a = 1;
var b = 2;
(function() {
return
a + b;
})() // undefined
// 會被自動轉(zhuǎn)換為
(function() {
return a + b;
})() // 3
也可以返回函數(shù)表達式,就是高階函數(shù)的定義榴徐,高階函數(shù)是一個接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為輸出返回的函數(shù)守问。
3. 其他語句
3.1 debugger
在程序中調(diào)用可用的調(diào)試功能,如設(shè)置斷點坑资。
debugger
3.2 導(dǎo)入/導(dǎo)出:export耗帕、import
export: 從模塊中導(dǎo)出函數(shù),對象或原始值袱贮,以便其他程序可以通過import語句使用仿便。
導(dǎo)出模式分為兩種:命名導(dǎo)出 和 默認導(dǎo)出≡芪。可以在每一個模塊中定義多個命名導(dǎo)出嗽仪,但是只允許一個默認導(dǎo)出。
導(dǎo)入/導(dǎo)出的模塊都是運行在嚴(yán)格模式下窑业。
導(dǎo)出示例:
export let name; // 導(dǎo)出單個屬性
export const myName = 'yang'; // 導(dǎo)出常量
export class ClassName {} // 導(dǎo)出類
export default defaultName; // 導(dǎo)出默認屬性
export {name1, name2...} // 導(dǎo)出列表
export {defaultName as default, name1 as Wang...} // 重命名導(dǎo)出钦幔,將 name1 作為默認屬性導(dǎo)出, name2 重命名為 Wang
// 模塊重定向,導(dǎo)入指定路徑的模塊并導(dǎo)出
export * from ...; // 導(dǎo)出指定模塊所有導(dǎo)出的屬性常柄,除了默認導(dǎo)出值
export {default} from ...; // 導(dǎo)出指定模塊中的默認導(dǎo)出值
export {name1, name2...} from ...; // 導(dǎo)出指定模塊中某些屬性
export {import1 as name1, import2 as name2...} from ...; // 重命名導(dǎo)出指定模塊中某些屬性
import: 導(dǎo)入由另一個模塊導(dǎo)出的綁定鲤氢。
瀏覽器中搀擂,import 語句只能在聲明了 type="module" 的 script 標(biāo)簽中使用。
還有一個類似函數(shù)動態(tài)的 import()卷玉,不需要依賴 type="module" 的 script 標(biāo)簽哨颂。
靜態(tài) import 更容易從代碼靜態(tài)分析工具和 tree shaking 中受益,動態(tài) import() 則在按需加載模塊時有用相种。
導(dǎo)入示例:
import * as names from 'export.js'; // 導(dǎo)入整個模塊內(nèi)容威恼,使用 names 模塊名稱作為命名空間
import {myName, ClassName} from 'export.js'; // 導(dǎo)入多個接口
import name from 'export.js'; // 導(dǎo)入默認接口(即用 export default 導(dǎo)出的接口)
import defaultName, {name1, newName as name2} from 'export.js'; // 同時導(dǎo)入默認接口和多個其他接口,并重命名其中某些接口
import defaultName, * as names from 'export.js'; // 同時導(dǎo)入默認接口和多個其他接口寝并,其他接口全部導(dǎo)入并重命名為 names
import 'export.js'; // 導(dǎo)入的模塊作為副作用導(dǎo)入(只運行模塊中的全局代碼)箫措,不導(dǎo)入模塊中的任何接口。
var promises = import('export.js'); // 可以像調(diào)用函數(shù)一樣來動態(tài)的導(dǎo)入模塊衬潦。以這種方式調(diào)用斤蔓,將返回一個 promise。
promises.then((module) => {})
3.3 label
在語句前加個可以引用的標(biāo)識符镀岛,可以和 break 或 continue 語句一起用弦牡。
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)格模式下漂羊,可以對函數(shù)聲明進行標(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 語句(不推薦椭豫,了解即可),用于擴展語句的作用域鏈旨指。
在 ECMAScript 5 嚴(yán)格模式中該標(biāo)簽已被禁止捻悯。推薦的替代方案是聲明一個臨時變量來承載你所需要的屬性。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
// 因為作用域中存在 Math 變量淤毛,所以先查找該變量中的 Math 對象是否有 PI 屬性今缚,發(fā)現(xiàn)沒有所以報錯。
// 'with' 語句將變量 Math 對象添加到作用域鏈的頂端低淡,在查找變量值 PI 時姓言,會在指定的對象 Math 中查找,發(fā)現(xiàn)沒有所以報錯蔗蹋。with 查找對象時何荚,會先從當(dāng)前作用域中查找,所以查找起來將會很慢猪杭。而且調(diào)試起來也會麻煩餐塘。
-
表達式語句
任何表達式都可以成為語句,就是說在任何需要寫語句的地方皂吮,都可以寫表達式戒傻,這樣的語句叫做表達式語句税手,表達式語句是一種特殊的語句。反過來需纳,我們不能在寫表達式的地方寫語句芦倒。
下圖是 ecma262 規(guī)范中 If 語句的語法。其中有 Statement 的地方都可以使用 Expression 即表達式不翩,例如下方示例中的 callback 函數(shù)調(diào)用表達式兵扬,就是替代了原來的語句,也是表達式語句口蝠。
ECMAScript 2020 If 語句語法
示例:
// callback 為表達式語句器钟,是一種特殊的語句 if (true) callback()
-
比較
1. 如何區(qū)分表達式和語句呢?
1.看是否產(chǎn)生值判斷妙蔗,對表達式求值一定會返回值俱箱,對語句求值未可能有返回值也可能沒有返回值;
2.看后面是否有分號灭必,有分號的一定是語句,沒有分號的可能是表達式也可能是語句乃摹。下面兩個例子禁漓,第一個能成功 log 的原因在于 if 語句括號里應(yīng)該為表達式,而 true 是表達式中的布爾值字面量孵睬。第二個
var a = 0
是聲明語句而不是表達式播歼,沒有返回值,所以會報錯掰读。if (true) { console.log('Hi'); } // 輸出: // Hi if (var a = 0) { console.log('Hi'); } // 輸出: // Uncaught SyntaxError: Unexpected token 'var'
2. 相似的表達式和語句
2.1 if 語句和條件表達式
if 語句和條件表達式表示的含義一樣,只是一個是語句,一個是表達式會返回值而已摩桶。
var x; var y = -1; // if 語句 if (y >= 0) { x = y; } else { x = -y; } // 條件表達式 x = (y >= 0 ? y : -y); // 括號不是必須的犀暑,加上括號更容易閱讀
2.2 函數(shù)聲明和函數(shù)表達式
函數(shù)表達式與函數(shù)聲明擁有幾乎相同的語法,但是有以下區(qū)別:
- 在函數(shù)表達式中可以省略函數(shù)名稱拢肆,省略函數(shù)名稱即為匿名函數(shù)减响;函數(shù)聲明中則不能省略函數(shù)名;
- 函數(shù)表達式可以用作 IIFE (即時調(diào)用函數(shù)表達式)郭怪,函數(shù)聲明不能用作 IIFE支示。
// 函數(shù)表達式:省略函數(shù)名 function () {} // 函數(shù)表達式:未省略函數(shù)名 // 寫法與函數(shù)聲明完全一致 function foo() {}
未省略函數(shù)名的函數(shù)表達式與函數(shù)聲明沒有區(qū)別,但是作用不同:函數(shù)表達式產(chǎn)生值鄙才,即函數(shù)颂鸿;函數(shù)聲明導(dǎo)致動作,創(chuàng)建一個變量攒庵,其值為函數(shù)嘴纺。
未省略函數(shù)名的函數(shù)表達式中的函數(shù)名只能在函數(shù)內(nèi)部自調(diào)用败晴,在函數(shù)外部調(diào)用會報錯。示例:
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 defined
2.3 對象字面量表達式和塊語句
我們知道對象字面量是表達式颖医,它的寫法為
{key: value}
形式位衩,塊語句是用{}
包裹的語句。當(dāng)塊里包含的是label
語句且label
語句后面是表達式語句的時候熔萧,對象字面量表達式和塊語句的寫法可能會完全一致糖驴。
示例:{ foo: bar(3, 5) }
上面的例子中既是對象字面量,又可以說是塊語句佛致。作為塊語句而言贮缕,塊里則是標(biāo)簽為 foo 的 label 語句,標(biāo)簽后的語句為一個函數(shù)調(diào)用表達式俺榆,根據(jù)前面對表達式語句的定義感昼, 可以知道
bar(3, 5)
是一個表達式語句。所以在程序中我們看到的
{}
有可能是字面量罐脊,有可能是塊語句定嗓,根據(jù)上下文情況區(qū)分∑甲溃看下面的例子:> [] + {} "[object Object]" > {} + [] 0
為什么兩個結(jié)果不一致呢宵溅?原因就在于前面的
{}
被作為字面量計算的,后面的是作為塊語句計算上炎。例中還涉及到隱式轉(zhuǎn)換的問題恃逻,此處埋個伏筆,后續(xù)我會單獨出一篇文章講解藕施。2.4 表達式中的逗號和語句中的分號
在 JavaScript 中寇损,語句是用分號隔離,例如
foo(); bar()
裳食;表達式可用逗號隔離矛市,例如foo(), bar()
,兩個表達式都會執(zhí)行诲祸,只是會返回后面的表達式的值尘盼。> "a", "b" 'b' > var x = ("a", "b"); > x 'b'
3. 使用對象字面量和函數(shù)表達式作為語句
我們已經(jīng)知道了表達式可以放在任何需要語句的地方,這種即表達式語句烦绳。對于某些表達式與語句沒有區(qū)別的情況卿捎,如 2.3 中的對象字面量和塊,2.4 中的函數(shù)表達式和函數(shù)聲明径密,如何區(qū)分是表達式還是語句呢午阵?一般情況下,是根據(jù)出現(xiàn)在表達式上下文還是語句上下文中區(qū)分,但是有一種情況例外底桂,就是表達式語句植袍。
因為表達式語句是一種特殊的語句,所以其上下文為語句上下文籽懦,這種情況中的
{}
會被當(dāng)作塊處理于个,function
開頭的語法會被當(dāng)作函數(shù)聲明。為了避免歧義暮顺,JavaScript語法禁止表達式語句使用{
和function
開頭厅篓。如果一定要用這兩個開頭的表達式,并且讓它們僅作為表達式處理捶码,則可以將表達式放到()
分組操作符中羽氮,使它們出現(xiàn)在表達式的上下文中,并且不會改變表達式結(jié)果惫恼。(Expression)
——分組操作符档押,由圓括號包裹表達式和子表達式,返回執(zhí)行表達式的結(jié)果祈纯。還有另一種方式確保在表達式上下文中解析表達式令宿,就是使用一元操作符,如
!
或+
等腕窥。但是與()
的不同點在于一元操作符會改變表達式的結(jié)果粒没。見下方立即調(diào)用函數(shù)表達式中的示例。下面看看
eval
和立即調(diào)用函數(shù)表達式中的應(yīng)用油昂。-
eval
eval 是在語句上下文中解析其參數(shù),所以例子中上面的示例將
{}
解析為塊倾贰,所以里面為 label 語句冕碟,因而輸出為123
。 加上括號后匆浙,{}
及其里面的內(nèi)容的上下文為表達式安寺,所以{foo: 123}
被視為字面量表達式,因而輸出為{foo: 123}
首尼。> eval("{foo: 123}"); 123 > eval("({ foo: 123 })") {foo: 123}
-
立即調(diào)用函數(shù)表達式(IIFEs)
// 立即調(diào)用函數(shù)表達式 > (function () { return "hello" }()) 'hello' > (function () { return "hello" })() 'hello' // 省略括號之后的立即調(diào)用函數(shù)表達式 > function () { return "hello" }() Uncaught SyntaxError: Function statements require a function name > +function () {console.log('hello')}() hello NaN // 返回 NaN 是因為表達式返回的為 undefined挑庶,所以 +undefined 為NaN > void function () {console.log('hello')}() hello undefined // 同理,void undefined 返回 undefined
有一點要注意的是软能,連續(xù)使用 IIFEs 時迎捺,要記得加分號,否則會報錯查排。因為后面的 IIFE 會把前面的 IIFE 的結(jié)果當(dāng)作函數(shù)調(diào)用凳枝。
若想省略分號,可以將一元運算符放在立即調(diào)用函數(shù)表達式前面,因為會有自動分號插入岖瑰。自動分號插入機制后續(xù)我也會講到叛买,此處埋上第二個伏筆。(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