语句和声明

JavaScript 应用程序是由许多语法正确的语句组成的。单个语句可以跨多行。如果每个语句用分号隔开,那么多个语句可以在一行中出现。

**语句和声明(按类别分类)

控制流程

Block

一个块语句可以用来管理零个或多个语句。该区块是由一对大括号分隔.
语法:
块声明:{ statement }
标记块声明: LabelIdentifier: { StatementList }

通过var声明的变量或者非严格模式下 (non-strict mode) 创建的函数声明没有块级作用域。

1
2
3
4
5
var x = 1;
{
var x = 2;
}
console.log(x); // 输出结果是 2,因为块中的 var x语句与块前面的var x语句作用域相同

使用let和const声明的变量是有块级作用域的

1
2
3
4
5
let x = 1;
{
let x = 2;
}
console.log(x); // 输出 1
1
2
3
4
5
const c = 1;
{
const c = 2;
}
console.log(c); // 输出 1,而且不会报错

使用function

函数声明同样被限制在声明他的语句块内:

1
2
3
4
5
6
7
foo('outside');  // TypeError: foo is not a function
{
function foo(location) {
console.log('foo is called ' + location);
}
foo('inside'); // 正常工作并且打印 'foo is called inside'
}

break

终止当前循环、switch或label语句,使程序跳到下一条语句执行。
语法:break [label]
label 可选,与语句标签相关联的标识符。如果 break 语句不在一个循环或 switch 语句中,则该项是必须的。

终止循环:

1
2
3
4
5
6
7
8
9
10
11
12
function testBreak(x) {
var i = 0;

while (i < 6) {
if (i == 3) {
break;
}
i += 1;
}

return i * x; // 当 i 为 3 时,会中止 while 循环,然后返回 3 * x 的值。
}

终止switch语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
const food = "sushi";

switch (food) {
case "sushi":
console.log("Sushi is originally from Japan.");
break;
case "pizza":
console.log("Pizza is originally from Italy.");
break;
default:
console.log("I have never heard of that dish.");
break;
}

终止label语句:
一个 break 语句必须内嵌在它引用的标记中.

1
2
3
4
5
6
7
8
block_1:{
console.log ('1');
break block_2; // SyntaxError: label not found
}

block_2:{
console.log ('2');
}
1
2
3
4
5
6
7
8
9
10
outer_block:{

inner_block:{
console.log ('1');
break outer_block; // breaks out of both inner_block and outer_block
console.log (':-('); // skipped
}

console.log ('2'); // skipped
}

break 语句不能在 function 函数体中直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function testBreak(x) {
var i = 0;

while (i < 6) {
if (i == 3) {
(function() {
break;
})();
}
i += 1;
}

return i * x;
}

testBreak(1); // SyntaxError: Illegal break statement
1
2
3
4
5
6
block_1: {
console.log('1');
( function() {
break block_1; // SyntaxError: Undefined label 'block_1'
})();
}

continue

终止执行当前或标签循环的语句,执行下一个迭代循环
语法:continue [label]

1
2
3
4
5
6
7
8
9
10
let text = '';

for (let i = 0; i < 10; i++) {
if (i === 3) {
continue;
}
text = text + i;
}

console.log(text); // "012456789"

与 break 语句的区别在于,continue 并不会终止循环的迭代,而是:

  • 在while循环中,控制流跳转回条件判断
  • 在for循环中,控制流跳转到更新语句

Empty

;
空语句用来表示没有语句的情况,尽管js语法期望有语句提供。

if…else

如果指定的条件是 true,则执行相匹配的一个语句,若为 false,则执行另一个语句。

switch

计算表达式,将子句于表达式的值做匹配,执行与该值相关联的语句。
使用 严格运算符 (en-US),===做匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
switch (expression) {
case value1:
// 当 expression 的结果与 value1 匹配时,执行此处语句
[break;]
case value2:
// 当 expression 的结果与 value2 匹配时,执行此处语句
[break;]
...
case valueN:
// 当 expression 的结果与 valueN 匹配时,执行此处语句
[break;]
[default:
// 如果 expression 与上面的 value 值都不匹配,执行此处语句
[break;]]
}

如果你忘记添加 break,那么代码将会从值所匹配的 case 语句开始运行,然后持续执行下一个 case 语句而不论值是否匹配。
可以把 default 放到 case 之间

switch语句内的块级作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const action = 'say_hello';
switch (action) {
case 'say_hello':
let message = 'hello';
console.log('0 ~5');
break;
case 'say_hi':
let message = 'hi';
case 6:
console.log('6');
break;
default:
console.log('Empty action received.');
break;
}
// Identifier 'message' has already been declared

导致这一问题的根本原因在于两个 let 语句处于同一个块级作用域,所以它们被认为是同一个变量名的重复声明。

通过把 case 语句包装到括号里面,我们就可以轻松解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const action = 'say_hello';
switch (action) {
case 'say_hello': {
let message = 'hello';
console.log('0 ~5');
break;
}
case 'say_hi':{
let message = 'hi';
}
case 6: {
console.log('6');
break;
}
default:{
console.log('Empty action received.');
break;
}
}

throw

抛出一个用户定义的异常。
当前函数的执行将被停止(throw 之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个 catch 块。如果调用者函数中没有 catch 块,程序将会终止。

语法:throw expression
expression: 要抛出的表达式

1
2
3
4
5
6
7
8
9
10
11
12
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
throw new Error('Parameter is not a number!');
}
}

try {
getRectArea(3, 'A');
} catch (e) {
console.error(e);
// Expected output: Error: Parameter is not a number!
}

try…catch

标记一个语句块,并指定一个应该抛出异常的反馈。

try语句包含了由一个或者多个语句组成的try块,和至少一个catch块或者一个finally块的其中一个,或者两个兼有,下面是三种形式的try声明:
1.try…catch
2.try…finally
3.try…catch…finally

1
2
3
4
5
6
7
try {
nonExistentFunction();
} catch (error) {
console.error(error);
// Expected output: ReferenceError: nonExistentFunction is not defined
// (Note: the exact output may be browser-dependent)
}

catch子句包含try块中抛出异常时要执行的语句。如果在try块中有任何一个语句(或者从try块中调用的函数)抛出异常,控制立即转向catch子句。如果在try块中没有异常抛出,会跳过catch子句。

finally子句在try块和catch块之后执行但是在下一个try声明之前执行。无论是否有异常抛出或捕获它总是执行。

嵌套try语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
try {
throw new Error("oops");
}
finally {
console.log("finally");
}
}
catch (ex) {
console.error("outer", ex.message);
}

// Output:
// "finally"
// "outer" "oops"

现在,如果我们已经在 try 语句中,通过增加一个 catch 语句块捕获了异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try {
try {
throw new Error("oops");
}
catch (ex) {
console.error("inner", ex.message);
throw ex;
}
finally {
console.log("finally");
}
}
catch (ex) {
console.error("outer", ex.message);
}

// Output:
// "inner" "oops"
// "finally"
// "outer" "oops"

从 finally 语句块返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function test() {
try {
try {
throw new Error("oops");
}
catch (ex) {
console.error("inner", ex.message);
throw ex;
}
finally {
console.log("finally");
return;
}
}
catch (ex) {
console.error("outer", ex.message);
}
}
test()

// 注:此 try catch 语句需要在 function 中运行才能作为函数的返回值,否则直接运行会报语法错误
// Output:
// "inner" "oops"
// "finally"

声明

var

var 语句 用于声明一个函数范围或全局范围的变量,并可将其初始化为一个值(可选)。

var声明的变量具有变量提升,“hoisting”就像是把所有的变量声明移动到函数或者全局代码的开头位置。这意味着变量可以在声明之前使用.
var创建的全局变量会作为window对象的属性

let

let 语句声明一个块级作用域的局部变量,并可以初始化为一个值(可选)。
与var区别:

  • var声明的变量作用域是全局或整个函数的,而let声明是块级的
  • let声明的变量不会在作用域中提升,他是编译时才初始化。
  • var创建的全局变量会作为window对象的属性,而let不会
  • var可以重复声明同一个变量,let不可以
  • var声明变量前访问undefined,let声明变量前访问抛出referenceError,暂时性死区

作用域规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function varTest() {
var x = 1;
{
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2 var 声明的变量的作用域是整个封闭函数
}

function letTest() {
let x = 1;
{
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}

在全局作用域中,let 和 var 不一样,它不会在全局对象上创建属性。例如:

1
2
3
4
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

重复声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let x = 1;

switch(x) {
case 0:
let foo;
break;
case 1:
let foo; // SyntaxError for redeclaration.
break;
}

if (x) {
let foo;
let foo; // SyntaxError thrown.
}

case子句同属一个作用域,将case用块{}包裹可以创建一个新的作用域的词法环境,就不会产生重复声明的错误。

1
2
3
4
5
6
7
8
9
10
11
12
let x = 1;

switch(x) {
case 0: {
let foo;
break;
}
case 1: {
let foo;
break;
}
}

暂时性死区: 从块作用域开始到执行声明变量的行之前,let或const声明的变量都处于“暂时性死区”(temporal dead zone, TDZ).
当变量处于暂时性死区时,其尚未初始化,访问变量会抛出ReferenceError.

1
2
3
4
5
6
{ // TDZ starts at beginning of scope
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2; // End of TDZ (for foo)
}

使用术语”temporal”是因为区域取决于代码执行的顺序(时间),而不是编写的顺序(位置)。

1
2
3
4
5
6
7
8
9
{
// TDZ starts at beginning of scope
const func = () => console.log(letVar); // OK

// Within the TDZ letVar access throws `ReferenceError`

let letVar = 3; // End of TDZ (for letVar)
func(); // Called outside TDZ! output: 3
}

暂时性死区与typeof:
如果使用typeof检测在暂时性死区中的变量,会抛出ReferenceError。

1
2
console.log(typeof i); // Uncaught ReferenceError: i is not defined
let i = 10;

与使用typeof检测值为undefined的未声明变量不同:

1
console.log(typeof i); // undefinded

暂时性死区和词法作用域:

1
2
3
4
5
6
7
function test() {
var foo = 33;
if(foo) {
let foo = (foo + 55); // Uncaught ReferenceError: Cannot access 'foo' before initialization
}
}
test();

if(foo),foo有值,执行if块内代码,表达式foo + 55, 此时foo还未初始化处于暂时先死区,会抛出ReferenceError

其他情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
let x = 1;

{
var x = 2; // Uncaught SyntaxError: Identifier 'x2' has already been declared
}

var y = 2;
{
let y = 2; // right
}

let z = 3;
var z = 3 // Uncaught SyntaxError: Identifier 'z' has already been declared

var声明会将变量提升到块的顶部,这会导致重复声明变量。

const

声明一个只读的命名常量。

  • 全局或块级作用域
  • 无法重复声明
  • 不能通过赋值改变
  • 全局常量不会变为 window 对象的属性
  • 必须在声明的同一语句中指定它的值

函数和类

function

声明一个指定参数的函数。

有条件的创建函数:

函数可以被有条件来声明,这意味着,函数声明可能出现在一个 if 语句里,但是,这种声明方式在不同的浏览器里可能有不同的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var hoisted = "foo" in this;
console.log(`'foo' name ${hoisted ? "is" : "is not"} hoisted. typeof foo is ${typeof foo}`);
if (false) { // 注意,即使把上面代码中的 if(false) 改为 if(true),结果也是一样的
function foo(){ return 1; }
}

// 在 Chrome 里:
// 'foo' 变量名被提升,但是 typeof foo 为 undefined
//
// 在 Firefox 里:
// 'foo' 变量名被提升。但是 typeof foo 为 undefined
//
// 在 Edge 里:
// 'foo' 变量名未被提升。而且 typeof foo 为 undefined
//
// 在 Safari 里:
// 'foo' 变量名被提升。而且 typeof foo 为 function

函数声明提升:

JavaScript 中的函数声明被提升到了函数定义。你可以在函数声明之前使用该函数:

1
2
3
4
5
hoisted(); // logs "foo"

function hoisted() {
console.log('foo');
}

函数表达式不会被提升:

1
2
3
4
5
notHoisted(); // TypeError: notHoisted is not a function

var notHoisted = function() {
console.log('bar');
};

对于同名的var变量和函数,在声明前访问,函数的优先级最高,声明后访问,取决于变量是否赋初始值:

  • 声明变量时只var声明未赋值,则函数会覆盖变量
  • 声明变量时指定了初始值,则变量覆盖函数
1
2
3
4
console.log(a) // ƒ a() {}
var a;
function a(){}
console.log(a) // ƒ a() {}
1
2
3
4
console.log(a) // ƒ a() {}
var a=10;
function a(){}
console.log(a) // 10

function*

function* 这种声明方式 (function关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator 对象

async function

使用指定的参数声明一个异步函数。

return

指return 语句终止函数的执行,并返回一个指定的值给函数调用者。

class

class声明创建一个基于原型继承的具有给定名称的新类。

迭代器

do…while

创建一个循环来执行语句,直到该语句条件表达式的值为 false。先执行语句,再执行条件表达式,该语句至少会执行一次。

for

创建一个由 3 个可选的表达式组成的循环,该循环用括号包裹,分号分割,并在循环体中执行语句。

for…in

for…in 语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var triangle = {a: 1, b: 2, c: 3};

function ColoredTriangle() {
this.color = 'red';
}

ColoredTriangle.prototype = triangle;

var obj = new ColoredTriangle();

for (var prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
// Output:
// "obj.color = red"
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

// 自身属性
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}

// Output:
// "obj.color = red"

for…of

for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句.

示例:

迭代Array:

1
2
3
4
5
6
7
8
let iterable = [10, 20, 30];

for (const value of iterable) {
console.log(value);
}
// 10
// 20
// 30

迭代String:

1
2
3
4
5
6
7
8
let iterable = "boo";

for (let value of iterable) {
console.log(value);
}
// "b"
// "o"
// "o"

迭代TypedArray:

1
2
3
4
5
6
7
let iterable = new Uint8Array([0x00, 0xff]);

for (let value of iterable) {
console.log(value);
}
// 0
// 255

迭代Map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);

for (let entry of iterable) {
console.log(entry);
}
// ["a", 1]
// ["b", 2]
// ["c", 3]

for (let [key, value] of iterable) {
console.log(value);
}
// 1
// 2
// 3

迭代Set:

1
2
3
4
5
6
7
8
let iterable = new Set([1, 1, 2, 2, 3, 3]);

for (let value of iterable) {
console.log(value);
}
// 1
// 2
// 3

迭代 arguments 对象:

1
2
3
4
5
6
7
8
9
(function() {
for (let argument of arguments) {
console.log(argument);
}
})(1, 2, 3);

// 1
// 2
// 3

迭代DOM集合:

1
2
3
4
5
6
//注意:这只能在实现了 NodeList.prototype[Symbol.iterator] 的平台上运行
let articleParagraphs = document.querySelectorAll("article > p");

for (let paragraph of articleParagraphs) {
paragraph.classList.add("read");
}

关闭迭代器:

1
2
3
4
5
6
7
8
9
10
function* foo(){
yield 1;
yield 2;
yield 3;
};

for (let o of foo()) {
console.log(o); // 1
break; // closes iterator, triggers return
}

for await…of

在异步可迭代对象、类数组对象、迭代器和生成器上迭代,调用自定义迭代钩子,其中包含要为每个不同属性的值执行的语句。

while

创建一个循环语句,循环会一直持续到该语句条件表达式的值为 false。先执行条件表达式,然后执行语句。

其他

debugger

调用可用的调试功能。如果没有调试功能可用,该语句不生效。

export

export 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们.

无论您是否声明,导出的模块都处于严格模式。export 语句不能用在嵌入式脚本中。

存在两种 exports 导出方式:

  • 命名导出(每个模块包含任意数量)
  • 默认导出(每个模块包含一个)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 导出单个特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}

// 导出列表
export { name1, name2, …, nameN };

// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };

// 解构导出并重命名
export const { name1, name2: bar } = o;

// 默认导出
export default expression;
export default function () { … } // also class, function*
export default function name1() { … } // also class, function*
export { name1 as default, … };

// 导出模块合集
export * from …; // does not set the default export
export * as name1 from …; // Draft ECMAScript® 2O21
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;

import

用来导入外部模块或script中导出的函数。

无论是否声明了 strict mode,导入的模块都运行在严格模式下。在浏览器中,import 语句只能在声明了 type=”module” 的 script 的标签中使用。

此外,还有一个类似函数的动态 import(),它不需要依赖 type=”module” 的 script 标签。

1
2
3
4
5
6
7
8
9
10
11
import defaultExport from "module-name";// 导入默认值
import * as name from "module-name"; // 导入整个模块的内容
import { export } from "module-name"; // 导入单个接口
import { export as alias } from "module-name"; // 导入带有别名的接口
import { export1 , export2 } from "module-name"; // 导入多个接口
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name"; // 导入时重命名接口
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name"; // 整个模块仅为副作用(中性词,无贬义含义)而导入,而不导入模块中的任何内容(接口)。这将运行模块中的全局代码,但实际上不导入任何值。
var promise = import("module-name");//这是一个处于第三阶段的提案。动态导入

import.meta

向 JavaScript 模块公开上下文特定的元数据的元属性。

label

带标识的语句,与break或continue语句一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
let str = '';

loop1:
for (let i = 0; i < 5; i++) {
if (i === 1) {
continue loop1;
}
str = str + i;
}

console.log(str);
// Expected output: "0234"

with

拓展一个语句的作用域.不建议使用