(一)语句和表达式
语句相当于句子,表达式相当于短语,运算符则相当于标点符号和连接词。
var a = 3 * 6;//语句 3*6是表达式
var b = a;//语句 a是表达式
b;//既是语句又是表达式
1.1)语句的结果值
获得结果值最直接的方法是在浏览器开发控制台中输入语句,默认情况下控制台会显示所
执行的最后一条语句的结果值。
/**规范定义 var 的结果值是undefined。**/
var a = 42;//语句结果值 undefined
/**在控制台 /REPL 中输入以上代码应该会显示 42,即最后一个语句 / 表达式 b = 4 + 38 的结果值。**/
var b;
if (true) {
b = 4 + 38;
}
/**语句结果值不能直接赋值给变量,但是可以通过do 表达式(ES7)或者eval函数(已不推荐使用)**/
var a, b;
a = do {
//语句结果值不能直接赋值给变量,象a=if(true){...}这样是不行的
if (true) {
b = 4 + 38;
}
};
//a=eval("if (true) {b = 4 + 38; }");这样也可以,但不要这么使用
a; // 42
1.2)表达式的副作用
表达式会对变量的值进行改变,既是副作用(有可能产生意想不到的结果).
function foo() {
a = a + 1;
}
var a = 1;
foo(); // 副作用:a的值被改变为2
var a = 42, b;
b = ( a++, a );//多个独立表达式串联,相当于b=a++; b=a;
a; // 43
b; // 43
1.3)上下文规则
标签:
/**在continue中的应用**/
// 标签为foo的循环
foo: for (var i=0; i<4; i++) {
for (var j=0; j<4; j++) {
// 如果j和i相等,继续外层循环
if (j == i) {
// 跳转到foo的下一个循环
continue foo;
}
// 跳过奇数结果
if ((j * i) % 2 == 1) {
// 继续内层循环(没有标签的)
continue;
}
console.log( i, j );
}
}
// 1 0
// 2 0
// 2 1
// 3 0
// 3 2
/**在break中的应用,可以跳出多层循环**/
// 标签为foo的循环
foo: for (var i=0; i<4; i++) {
for (var j=0; j<4; j++) {
if ((i * j) >= 3) {
console.log( "stopping!", i, j );
break foo;
}
console.log( i, j );
}
}
// 0 0
// ...省略循环结果
// 1 2
// stopping! 1 3
标签也能用于非循环代码块,但只有 break 才可以。
// 标签为bar的代码块,比较少见也不建议使用
function foo() {
bar: {
console.log( "Hello" );
break bar;
console.log( "never runs" );
}
console.log( "World" );
}
foo();
// Hello
// World
一个特殊的涉及强制转换的例子:
[] + {}; // "[object Object]" []转换成"",{}的toString为"[object Object]"
{} + []; // 0 这个{}被当成空的代码块,[]->""->0
对象解构:
ES6 开始,{ … } 也可用于“解构赋值”,特别是对象的解构。
function getData() {
// ..
return {
a: 42,
b: "foo"
};
}
var { a, b } = getData();
console.log( a, b ); // 42 "foo"
//{ a , b } = .. 就是 ES6 中的解构赋值,相当于下面的代码:
var res = getData();
var a = res.a;
var b = res.b;
//精简对象作为形参
function foo({ a, b, c }) {
// 不再需要这样:
// var a = obj.a, b = obj.b, c = obj.c
console.log( a, b, c );
}
(二)运算符优先级
js中运算符优先级统计:
优先级 | 运算类型 | 关联性 | 运算符 |
---|---|---|---|
1 | 圆括号 | 无 | ( … ) |
2 | 成员访问 | 从左到右 | … . … |
需计算的成员访问 | 从左到右 | … [ … ] | |
new (带参数列表) | 无 | new… ( … ) | |
函数调用 | 从左到右 | … ( … ) | |
3 | new (无参数列表) | 从右到左 | new… |
4 | 后置递增、后置递减(运算符在后) | 无 | … ++、… - - |
5 | 逻辑非、按位非 | 从右到左 | !.. 、~… |
一元加法、一元减法 | 从右到左 | +…、-… | |
前置递增、前置递减 | 从右到左 | ++…、- -… | |
typeof、void、delete、await | 从右到左 | typeof…、void…、delete…、await… | |
6 | 幂 | 从右到左 | …**… |
7 | 乘法、除法、取模 | 从左到右 | …*…、…/…、…%… |
8 | 加法、减法 | 从左到右 | …+…、…-… |
9 | 按位左移、按位右移、无符号右移 | 从左到右 | …<<…、…>>…、…>>>… |
10 | 小于、小等于、大于、大等于 | 从左到右 | …<…、…<=…、…>…、…>=… |
in、instanceof | 从左到右 | …in…、…instanceof… | |
11 | 等号、不等、全等号,不全等 | 从左到右 | ==、!=、===、!== |
12 | 按位与 | 从左到右 | … & … |
13 | 按位异或 | 从左到右 | … ^ … |
14 | 按位或 | 从左到右 | … | … |
15 | 逻辑与 | 从左到右 | … && … |
16 | 逻辑或 | 从左到右 | … || … |
17 | 条件运算符 | 从右到左 | … ? … : … |
18 | 赋值 | 从右到左 | …=…, +=, -=, *=, /=, %=, <<=, >>=, >>>=, &=, |=, ^= |
19 | yield、yield* | 从右到左 | yield…、yield*… |
20 | 展开运算符 | 无 | … … |
21 | 逗号 | 从右到左 | …,… |
var a = 42, b;
b = ( a++, a );
a; // 43
b; // 43
//如果去掉 ( ) 会出现什么情况?
var a = 42, b;
b = a++, a;
a; // 43
b; // 42
//原因是 , 运算符的优先级比 = 低。所以 b = a++, a 其实可以理解为 (b = a++), a。
2.1)短路
对 && 和 || 来说,如果从左边的操作数能够得出结果,就可以忽略右边的操作数。我们将
这种现象称为“短路”(即执行最短路径)。
&&左边为假值,则不会执行右边的操作数;
||左边为真值,则不会执行右边的操作数;
2.2)关联
一般说来,运算符的关联不是从左到右就是从右到左,这取决于组合是从左开始还是从右开始。
请注意:关联和执行顺序(js的执行顺序是从左向右的)不是一回事。
var a = 42;
var b = "foo";
var c = false;
var d = a && b || c ? c || b ? a : c && b : a;
//?:是右关联,因此先找到(c || b ? a : c && b)
//等价于((a && b) || c) ? ((c || b) ? a : (c && b)) : a
d; // 42
建议:如果运算符优先级 / 关联规则能够令代码更为简洁,就使用运算符优先级 / 关联规则,例如if(a&&b&&c)而不是if((a&&b)&&c);
而如果 ( ) 有助于提高代码可读性,就使用 ( ),例如((a && b) || c) ? ((c || b) ? a : (c && b)) : a,便于理解。
(三)自动分号插入
有时 JavaScript 会自动为代码行补上缺失的分号,即自动分号插入(Automatic Semicolon Insertion,ASI)。
var a = 42, b
c;//独立表达式,若第一行为var a = 42, b, ASI则不会自动补上分号
//ASI 在某些情况下很有用,比如:
var a = 42;
do {
// ..
} while (a) // <-- 这里应该有;ASI会自动补上
a;
建议在所有需要的地方加上分号,将对 ASI 的依赖降到最低。
(四)语法错误
function foo(a,b,a) { } // 没问题
function bar(a,b,a) { "use strict"; } // 错误!
(function(){
"use strict";
var a = {
b: 42,
b: 43
}; // 错误!
})();
{
a = 2; // ReferenceError!
let a;
}
{
typeof a; // undefined
typeof b; // ReferenceError! TDZ(Temporal Dead Zone,暂时性死区)
let b;
}
(五)函数参数
只需遵守一个原则,将命名参数和 arguments 数组混用也不会出错:即不要同时访问命名参数和其对应的 arguments 数组单元。
/**虽然参数 a 和 b 都有默认值,但是函数不带参数时,arguments 数组为空。
相反,如果向函数传递 undefined 值,则 arguments 数组中会出现一个值为 undefined 的单元,而不是默认值。**/
function foo( a = 42, b = a + 1 ) {
console.log(
arguments.length, a, b,
arguments[0], arguments[1]
);
}
foo(); // 0 42 43 undefined undefined
foo( 10 ); // 1 10 11 10 undefined
foo( 10, undefined ); // 2 10 11 10 undefined
foo( 10, null ); // 2 10 null 10 null
//即使将命名参数和 arguments 数组混用也不会出错:
//只需遵守一个原则,即不要同时访问命名参数和其对应的 arguments 数组单元。
function foo(a) {
console.log( a + arguments[1] ); // aarguments[0],访问arguments[1]安全!
}
foo( 10, 32 ); // 42
(六)try…finally
try 可以和 catch 或者 finally配对使用,并且必要时两者可同时出现。
- finally先执行,在执行try中的return
function foo() {
try {
return 42;
}
finally {
console.log( "Hello" );
}
console.log( "never runs" );
}
console.log( foo() );
// Hello
// 42
- 如果 finally 中抛出异常,函数就会在此处终止。如果此前 try 中 已经有 return 设置了返回值,则该值会被丢弃。
- 如果 try中抛出异常,finally仍会执行,执行完再抛出异常。
function foo() {
try {
return 42;
}
finally {
throw "Oops!";
}
console.log( "never runs" );
}
console.log( foo() );
// Uncaught Exception: Oops!
function foo() {
try {
throw 42;
}
finally {
console.log( "Hello" );
}
console.log( "never runs" );
}
console.log( foo() );
// Hello
// Uncaught Exception: 42
- continue 在每次循环之后,会在 i++ 执行之前执行 console.log(i),所以结果是 0…9 而非1…10。
for (var i=0; i<10; i++) {
try {
continue;
}
finally {
console.log( i );
}
}
// 0 ... 9
(七)switch
- switch()内的参数值是要和case后的值严格相等的,否则不会进入条件内。
- 若不加break;代码会一直往下执行。
- default并非必须的,放在非最后一行,会出现其他结果。
//a 和 case 表达式的匹配算法与 ===相同。通常 case 语句中的 switch都是简单值,所以这并没有问题。
switch (a) {
case 2:
// 执行一些代码
break;
case 42:
// 执行另外一些代码
break;
default:
// 执行缺省代码
}
//复杂的switch
var a = "42";
switch (true) {
case a == 10:
console.log( "10 or '10'" );
break;
case a == 42;
console.log( "42 or '42'" );
break;
default:
// 永远执行不到这里
}// 42 or '42'
//break和default
//default 是可选的,并非必不可少(虽然惯例如此)。break 相关规则对 default 仍然适用:
var a = 10;
switch (a) {
case 1:
case 2:
// 永远执行不到这里
default:
console.log( "default" );
case 3:
console.log( "3" );
break;
case 4:
console.log( "4" );
}
// default
// 3