一、运算符
1. 算数运算符
10 个算数运算符
加法运算符:x + y
减法运算符: x - y
乘法运算符: x * y
除法运算符:x / y
指数运算符:x ** y
余数运算符:x % y
自增运算符:++x 或者 x++
自减运算符:--x 或者 x--
数值运算符: +x
负数值运算符:-x
加法运算符
- 加法运算符是在运行时决定到底是执行相加,还是执行连接
- 运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)
- 除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载
- 它们的规则是:所有运算子一律转为数值,再进行相应的数学运算
// 数值相加
1 + 1 // 2
true + true // 2
1 + true // 2
// 遇到字符串会执行连接
'a' + 'bc' // "abc"
1 + 'a' // "1a"
false + 'a' // "falsea"
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
1 - '2' // -1
1 * '2' // 2
1 / '2' // 0.5
// 如果运算子是对象,必须先转成原始类型的值,然后再相加
var obj = { p: 1 };
obj + 2 // "[object Object]2"
// JavaScript 自动调用对象的valueOf方法,再自动调用对象的toString方法
var obj = { p: 1 };
obj.valueOf().toString() // "[object Object]"
// 知道了这个规则以后,就可以自己定义valueOf方法或toString方法,得到想要的结果
// 自定义 vuLueOf
var obj = {
valueOf: function () {
return 1;
}
};
obj + 2 // 3
// 自定义 toString
var obj = {
toString: function () {
return 'hello';
}
};
obj + 2 // "hello2"
// 特例,如果运算子是一个Date对象的实例,那么会优先执行toString方法
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"
// 此刻 toString 方法优先执行
余数运算符
- 运算结果的正负号由第一个运算子的正负号决定
12 % 5 // 2
-1 % 2 // -1
1 % -2 // 1
// 为了得到负数的正确余数值,可以先使用绝对值函数
function isOdd(n) {
return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
自增和自减运算符
var x = 1;
++x // 2
x // 2
--x // 1
x // 1
var x = 1;
var y = 1;
x++ // 1
++y // 2
数值运算符,负数值运算符
- 数值运算符的作用在于可以将任何值转为数值(与Number函数的作用相同)
- 负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反
- 连用两个负数值运算符,等同于数值运算符
- 数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值
+true // 1
+[] // 0
+{} // NaN
var x = 1;
-x // -1
-(-x) // 1
指数运算符
- 注意,指数运算符是右结合,而不是左结合
- 即多个指数运算符连用时,先进行最右边的计算
2 ** 4 // 16
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2 // 512
赋值运算符
// 将 1 赋值给变量 x
var x = 1;
// 将变量 y 的值赋值给变量 x
var x = y;
// 等同于 x = x + y
x += y
// 等同于 x = x - y
x -= y
// 等同于 x = x * y
x *= y
// 等同于 x = x / y
x /= y
// 等同于 x = x % y
x %= y
// 等同于 x = x ** y
x **= y
// 等同于 x = x >> y
x >>= y
// 等同于 x = x << y
x <<= y
// 等同于 x = x >>> y
x >>>= y
// 等同于 x = x & y
x &= y
// 等同于 x = x | y
x |= y
// 等同于 x = x ^ y
x ^= y
2. 比较运算符
8 个比较运算符
- 相等比较
- 非相等比较,看是否都是字符串
- 是,按照字典顺序比较(比较 Unicode 码)
- 不是,转成数值,再比较数值的大小
> 大于运算符
< 小于运算符
<= 小于或等于运算符
>= 大于或等于运算符
== 相等运算符
=== 严格相等运算符
!= 不相等运算符
!== 严格不相等运算符
非相等运算符:字符串的比较
'cat' > 'dog' // false
'cat' > 'catalog' // false
'cat' > 'Cat' // true'
'大' > '小' // false
非相等运算符:非字符串的比较
- 如果两个运算子都是原始类型的值,则是先转成数值再比较
- 如果运算子是对象,会转为原始类型的值,再进行比较
// 原始类型值
5 > '4' // true
// 等同于 5 > Number('4')
// 即 5 > 4
true > false // true
// 等同于 Number(true) > Number(false)
// 即 1 > 0
2 > true // true
// 等同于 2 > Number(true)
// 即 2 > 1
// 任何值(包括NaN本身)与NaN比较,返回的都是false
1 > NaN // false
1 <= NaN // false
'1' > NaN // false
'1' <= NaN // false
NaN > NaN // false
NaN <= NaN // false
// 对象
var x = [2];
x > '11' // true
// 等同于 [2].valueOf().toString() > '11'
// 即 '2' > '11'
// 如果运算子是对象,会转为原始类型的值,再进行比较
// 先调用valueOf方法;如果返回的还是对象,再接着调用toString方法
x.valueOf = function () { return '1' };
x > '11' // false
// 等同于 [2].valueOf() > '11'
// 即 '1' > '11'
// 两个对象的比较也是如此
[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'
[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'
{ x: 2 } >= { x: 1 } // true
// 等同于 { x: 2 }.valueOf().toString() >= { x: 1 }.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'
严格相等运算符
- JavaScript 提供两种相等运算符:==和===
1 === "1" // false
true === "true" // false
1 === 0x1 // true
NaN === NaN // false
+0 === -0 // true
// 两个复合类型(对象、数组、函数)的数据比较时
// 不是比较它们的值是否相等,而是比较它们是否指向同一个地址
{} === {} // false
[] === [] // false
(function () {} === function () {}) // false
// 如果两个变量引用同一个对象,则它们相等
var v1 = {};
var v2 = v1;
v1 === v2 // true
// 对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值
var obj1 = {};
var obj2 = {};
obj1 > obj2 // false
obj1 < obj2 // false
obj1 === obj2 // false
// undefined 和 null
undefined === undefined // true
null === null // true
var v1;
var v2;
v1 === v2 // true
严格不相等运算符
- 它的算法就是先求严格相等运算符的结果,然后返回相反值
1 !== '1' // true
// 等同于
!(1 === '1')
相等运算符和不相等运算符
- 建议不使用
3. 布尔运算符
4 个布尔运算符
取反运算符:!
且运算符:&&
或运算符:||
三元运算符:?:
取反运算符(!)
!true // false
!false // true
!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true
!54 // false
!'hello' // false
![] // false
!{} // false
// 如果对一个值连续做两次取反运算,等于将其转为对应的布尔值
// 与Boolean函数的作用相同。这是一种常用的类型转换的写法
!!x
// 等同于
Boolean(x)
且运算符
- 且运算符(&&)往往用于多个表达式的求值
- 它的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值)
- 如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值
't' && '' // ""
't' && 'f' // "f"
't' && (1 + 2) // 3
'' && 'f' // ""
'' && '' // ""
var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1
// 这种跳过第二个运算子的机制,被称为“短路”
if (i) {
doSomething();
}
// 等价于
i && doSomething();
true && 'foo' && '' && 4 && 'foo' && true
// ''
1 && 2 && 3
// 3
或运算符(||)
't' || '' // "t"
't' || 'f' // "t"
'' || 'f' // "f"
'' || '' // ""
// 短路规则对这个运算符也适用
var x = 1;
true || (x = 2) // true
x // 1
false || 0 || '' || 4 || 'foo' || true
// 4
false || 0 || ''
// ''
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
// 或运算符常用于为一个变量设置默认值
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
三元条件运算符(?:)
't' ? 'hello' : 'world' // "hello"
0 ? 'hello' : 'world' // "world"
console.log(true ? 'T' : 'F');
4. 二进制位运算符
7 个二进制位运算符
- 这些位运算符直接处理每一个比特位(bit)
- 好处是速度极快,缺点是很不直观
- 位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行
- 许多场合不能使用它们,否则会使代码难以理解和查错
二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1
二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0
二进制否运算符(not):符号为~,表示对一个二进制位取反
异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0
左移运算符(left shift):符号为<<,详见下文解释
右移运算符(right shift):符号为>>,详见下文解释
头部补零的右移运算符(zero filled right shift):符号为>>>,详见下文解释
5. 其他运算符,运算顺序
void 运算符
- 作用是执行一个表达式,然后不返回任何值,或者说返回undefined
- 这个运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超级链接中插入代码防止网页跳转
void 0 // undefined
void(0) // undefined
// 建议采用后一种形式,即总是使用圆括号
// 因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果
// 比如,void 4 + 7实际上等同于(void 4) + 7
// 小例子
var x = 3;
void (x = 5) //undefined
x // 5
<a href="http://example.com" onclick="f(); return false;">点击</a>
// 换成以下
<a href="javascript: void(f())">文字</a>
// 用户点击链接提交表单,但是不产生页面跳转
<a href="javascript: void(document.form.submit())">
提交
</a>
逗号运算符
- 用于对两个表达式求值,并返回后一个表达式的值
- 逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作
'a', 'b' // "b"
var x = 0;
var y = (x++, 10);
x // 1
y // 10
var value = (console.log('Hi!'), true);
// Hi!
value // true
优先级
- 记住所有运算符的优先级,是非常难的,也是没有必要的
- 圆括号的优先级是最高的,即圆括号中的表达式会第一个运算
- 顺便说一下,圆括号不是运算符,而是一种语法结构,一共有两种用法
- 一种是把表达式放在圆括号之中,提升运算的优先级
- 另一种是跟在函数的后面,作用是调用函数
- 因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级
// 该代码可以运行,这验证了圆括号只改变优先级,不会求值
var x = 1;
(x) = 2;
(expression)
// 等同于
expression
// 函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数
function f() {
return 1;
}
(f) // function f(){return 1;}
f() // 1
// 圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错
(var a = 1)
// SyntaxError: Unexpected token var
左结合与右结合
- 对于优先级别相同的运算符,同时出现的时候,就会有计算顺序的问题
// 左结合
x + y + z
// JS 引擎解释如下
(x + y) + z
// 右结合
w = x = y = z;
q = a ? b : c ? d : e ? f : g;
// JS 引擎解释如下
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));
// 指数运算符(**)也是右结合
2 ** 3 ** 2
// 相当于 2 ** (3 ** 2)
// 512
二、语法专题
1. 数据类型转换
- JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值
- 虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的
- 如果运算符发现,运算子的类型与预期不符,就会自动转换类型
var x = y ? 1 : 'a';
// 减法运算符预期左右两侧的运算子应该是数值
'4' - '3' // 1
强制转换
- Number()
- String()
- Boolean()
Number()
- 使用Number函数,可以将任意类型的值转化成数值,分成两种情况讨论
- 一种是参数是原始类型的值
- 另一种是参数是对象
// 原始类型值
// 数值:转换后还是原来的值
Number(324) // 324
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined:转成 NaN
Number(undefined) // NaN
// null:转成0
Number(null) // 0
// Number函数将字符串转为数值,要比parseInt函数严格很多
// 基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN
parseInt('42 cats') // 42
Number('42 cats') // NaN
另外,parseInt和Number函数都会自动过滤一个字符串前导和后缀的空格
parseInt('\t\v\r12.34\n') // 12
Number('\t\v\r12.34\n') // 12.34
// 对象
// 简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5.
// 第一步,调用对象自身的valueOf方法,如果返回原始类型的值,则直接对该值使用Number函数
// 第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法
// 第三步,如果toString方法返回原始类型的值,则对该值使用Number函数
// 第四步,如果toString方法返回的是对象,就报错
var obj = {x: 1};
Number(obj) // NaN
// 等同于
if (typeof obj.valueOf() === 'object') {
Number(obj.toString());
} else {
Number(obj.valueOf());
}
Number({}) // NaN
var obj = {
valueOf: function () {
return {};
},
toString: function () {
return {};
}
};
Number(obj)
// TypeError: Cannot convert object to primitive value
Number({
valueOf: function () {
return 2;
}
})
// 2
Number({
toString: function () {
return 3;
}
})
// 3
Number({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// 2
String()
- 可以将任意类型的值转化成字符串
// 原始数据类型
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
// 对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
// 第一步,先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数
// 如果toString方法返回的是对象,再调用原对象的valueOf方法
// 如果valueOf方法返回原始类型的值,则对该值使用String函数
// 如果valueOf方法返回的是对象,就报错
String({a: 1})
// "[object Object]"
// 等同于
String({a: 1}.toString())
// "[object Object]"
// 如果toString法和valueOf方法,返回的都是对象,就会报错
var obj = {
valueOf: function () {
return {};
},
toString: function () {
return {};
}
};
String(obj)
// TypeError: Cannot convert object to primitive value
// 自定义toString方法,改变返回值的例子
String({
toString: function () {
return 3;
}
})
// "3"
String({
valueOf: function () {
return 2;
}
})
// "[object Object]"
String({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// "3"
Boolean()
- 可以将任意类型的值转为布尔值
// 除了以下 5 个 falsy 值,其他全为 true
// undefined null 0 NaN ''
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean(true) // true
Boolean(false) // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
自动转换
- 预期什么类型的值,就调用该类型的转换函数
- 由于自动转换具有不确定性,而且不易除错
- 建议在预期为布尔值、数值、字符串的地方
- 全部使用Boolean、Number和String函数进行显式转换
123 + 'abc' // "123abc"
if ('abc') {
console.log('hello')
} // "hello"
+ {foo: 'bar'} // NaN
- [1, 2, 3] // NaN
// 自动转换为布尔值
// JavaScript 遇到预期为布尔值的地方(比如if语句的条件部分)
// 就会将非布尔值的参数自动转换为布尔值
// 系统内部会自动调用Boolean函数
if ( !undefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} // true
// 将一个表达式转为布尔值
// 写法一
expression ? true : false
// 写法二
!! expression
// 自动转换为字符串
// 遇到预期为字符串的地方,就会将非字符串的值自动转为字符串
// 先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
// 这种自动转换很容易出错
var obj = {
width: '100'
};
obj.width + 20 // "10020",开发者可能期望 120
// 自动转换为数值
//遇到预期为数值的地方,就会将参数值自动转换为数值
// 系统内部会自动调用Number函数
// 除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
// 注意:null转为数值时为0,而undefined转为数值时为NaN
// 一元运算符也会把运算子转成数值
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0
2. 错误处理机制
Error 实例对象
// message:错误提示信息
var err = new Error('出错了');
err.message // "出错了"
// name:错误名称(非标准属性)
if (error.name) {
console.log(error.name + ': ' + error.message);
}
// stack属性用来查看错误发生时的堆栈。
function throwit() {
throw new Error('');
}
function catchit() {
try {
throwit();
} catch(e) {
console.log(e.stack); // print stack trace
}
}
catchit()
// Error
// at throwit (~/examples/throwcatch.js:9:11)
// at catchit (~/examples/throwcatch.js:3:9)
// at repl:1:5
原生错误类型
// SyntaxError对象是解析代码时发生的语法错误
// 变量名错误
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token
// 缺少括号
console.log 'hello');
// Uncaught SyntaxError: Unexpected string
// ReferenceError对象是引用一个不存在的变量时发生的错误
// 使用一个不存在的变量
unknownVariable
// Uncaught ReferenceError: unknownVariable is not defined
// 等号左侧不是变量
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment
// RangeError对象是一个值超出有效范围时发生的错误
// 主要有几种情况,一是数组长度为负数
// 二是Number对象的方法参数超出范围,以及函数堆栈超过最大值
// 数组长度不得为负数
new Array(-1)
// Uncaught RangeError: Invalid array length
// TypeError对象是变量或参数不是预期类型时发生的错误
new 123
// Uncaught TypeError: number is not a func
var obj = {};
obj.unknownMethod() // obj.unknownMethod的值是undefined,而不是一个函数
// Uncaught TypeError: obj.unknownMethod is not a function
// URIError对象是 URI 相关函数的参数不正确时抛出的错误
// 主要涉及encodeURI()、decodeURI()、encodeURIComponent()
// decodeURIComponent()、escape()和unescape()这六个函数
decodeURI('%2')
// URIError: URI malformed
// EvalError 对象,eval函数没有被正确执行时,会抛出EvalError错误
// 该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留
// 总结
以上这6种派生错误,连同原始的Error对象,都是构造函数
开发者可以使用它们,手动生成错误对象的实例
这些构造函数都接受一个参数,代表错误提示信息(message)
var err1 = new Error('出错了!');
var err2 = new RangeError('出错了,变量超出有效范围!');
var err3 = new TypeError('出错了,变量类型无效!');
err1.message // "出错了!"
err2.message // "出错了,变量超出有效范围!"
err3.message // "出错了,变量类型无效!"
自定义错误
// 除了 JavaScript 原生提供的错误对象,还可以定义自己的错误对象
function UserError(message) {
this.message = message || '默认信息';
this.name = 'UserError';
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
// 生成这种自定义类型的错误的实例
new UserError('这是自定义的错误!');
throw 语句
// throw语句的作用是手动中断程序执行,抛出一个错误
if (x <= 0) {
throw new Error('x 必须为正数');
}
// Uncaught ReferenceError: x is not defined
// throw也可以抛出自定义错误
function UserError(message) {
this.message = message || '默认信息';
this.name = 'UserError';
}
throw new UserError('出错了!');
// Uncaught UserError {message: "出错了!", name: "UserError"}
// 对于 JavaScript 引擎来说,遇到throw语句,程序就中止了
// 引擎会接收到throw抛出的信息,可能是一个错误实例,也可能是其他类型的值
try…catch 结构
// 一旦发生错误,程序就中止执行了
// JavaScript 提供了try...catch结构,允许对错误进行处理,选择是否往下执行
try {
throw new Error('出错了!');
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
// Error: 出错了!
// at <anonymous>:3:9
// ...
// 常用方法
try {
f();
} catch(e) {
// 处理错误
}
try {
throw "出错了";
} catch (e) {
console.log(111);
}
console.log(222);
// 111
// 222
catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构
var n = 100;
try {
throw n;
} catch (e) {
if (e <= 50) {
// ...
} else {
throw e;
}
}
// Uncaught 100
// 为了捕捉不同类型的错误,catch代码块之中可以加入判断语句
try {
foo.bar();
} catch (e) {
if (e instanceof EvalError) {
console.log(e.name + ": " + e.message);
} else if (e instanceof RangeError) {
console.log(e.name + ": " + e.message);
}
// ...
}
finally 代码块
// try...catch结构允许在最后添加一个finally代码块
// 表示不管是否出现错误,都必需在最后运行的语句
function cleansUp() {
try {
throw new Error('出错了……');
console.log('此行不会执行');
} finally {
console.log('完成清理工作');
}
}
cleansUp()
// 完成清理工作
// Uncaught Error: 出错了……
// at cleansUp (<anonymous>:3:11)
// at <anonymous>:10:1
// 上面代码中,由于没有catch语句块,一旦发生错误,代码就会中断执行
// 中断执行之前,会先执行finally代码块,然后再向用户提示报错信息
var count = 0;
function countUp() {
try {
return count;
} finally {
count++;
}
}
countUp()
// 0
count
// 1
// 上面代码说明,return语句里面的count的值,是在finally代码块运行之前就获取了
// 下面是finally代码块用法的典型场景
openFile();
try {
writeFile(Data);
} catch(e) {
handleError(e);
} finally {
closeFile();
}
// 下面例子充分反映了try...catch...finally这三者之间的执行顺序
function f() {
try {
console.log(0);
throw 'bug';
} catch(e) {
console.log(1);
return true; // 这句原本会延迟到 finally 代码块结束再执行
console.log(2); // 不会运行
} finally {
console.log(3);
return false; // 这句会覆盖掉前面那句 return
console.log(4); // 不会运行
}
console.log(5); // 不会运行
}
var result = f();
// 0
// 1
// 3
result
// false
// catch代码块之中,触发转入finally代码块的标志,不仅有return语句,还有throw语句
function f() {
try {
throw '出错了!';
} catch(e) {
console.log('捕捉到内部错误');
throw e; // 这句原本会等到finally结束再执行
} finally {
return false; // 直接返回
}
}
try {
f();
} catch(e) {
// 此处不会执行
console.log('caught outer "bogus"');
}
// 捕捉到内部错误
// try代码块内部,还可以再使用try代码块
try {
try {
consle.log('Hello world!'); // 报错
}
finally {
console.log('Finally');
}
console.log('Will I run?');
} catch(error) {
console.error(error.message);
}
// Finally
// consle is not defined
// 上面代码中,try里面还有一个try。内层的try报错(console拼错了)
这时会执行内层的finally代码块,然后抛出错误,被外层的catch捕获
3. 编程风格
- 编译器的规范叫做“语法规则”(grammar),这是程序员必须遵守的
- 而编译器忽略的部分,就叫“编程风格”(programming style),这是程序员可以自由选择的
// 缩进
// 不要一会使用 Tab 键,一会使用空格键
// 区块
// 如果循环和判断的代码体只有一行,JavaScript 允许该区块(block)省略大括号
if (a)
b();
c();
// 等同与
if (a) {
b();
}
c();
// JavaScript 要使用起首的大括号跟在关键字的后面
// 因为 JavaScript 会自动添加句末的分号,导致一些难以察觉的错误
block {
// ...
}
// 圆括号
// 圆括号(parentheses)在 JavaScript 中有两种作用
// 一种表示函数的调用,另一种表示表达式的组合
// 圆括号表示函数的调用
console.log('abc');
// 圆括号表示表达式的组合
(1 + 2) * 3
// 建议可以用空格,区分这两种不同的括号
// 表示函数调用时,函数名与左括号之间没有空格。
// 表示函数定义时,函数名与左括号之间没有空格。
// 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。
// 行尾的分号
// 分号表示一条语句的结束, JavaScript 允许省略行尾的分号
// 为了方便使用,我不加分号
// 并额外记忆一些,规则,不要括号开头,不要奇奇怪怪的字符开头
// 大多数情况下,JavaScript 会自动添加分号
var a = 1
// 等同于
var a = 1;
// 全局变量
// JavaScript 最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写
// 因此,建议避免使用全局变量
// 如果不得不使用,可以考虑用大写字母表示变量名
这样更容易看出这是全局变量,比如UPPER_CASE
// 变量声明
// JavaScript 会自动将变量声明“提升”(hoist)到代码块(block)的头部
if (!x) {
var x = {};
}
// 等同于
var x;
if (!x) {
x = {};
}
// 了避免可能出现的问题,最好把变量声明都放在代码块的头部
// with 语句
with可以减少代码的书写,但是会造成混淆
with (o) {
foo = bar;
}
// 可能有四种运行结果
o.foo = bar;
o.foo = o.bar;
foo = bar;
foo = o.bar;
// 这四种结果都可能发生,取决于不同的变量是否有定义
// 因此,不要使用with语句
// 相等和严格相等
// 相等运算符会自动转换变量类型,造成很多意想不到的情况
0 == ''// true
1 == true // true
2 == true // false
0 == '0' // true
false == 'false' // false
false == '0' // true
' \t\r\n ' == 0 // true
// 建议不要使用相等运算符(==),只使用严格相等运算符(===)
// 语句的合并
a = b;
if (a) {
// ...
}
// 有人喜欢写成这样的
//虽然语句少了一行,但是可读性大打折扣,而且会造成误读
if (a = b) {
// ...
}
// 自增和自减运算符
// 自增(++)和自减(--)运算符,放在变量的前面或后面,返回的值不一样,很容易发生错误
// 事实上,所有的++运算符都可以用+= 1代替
++x
// 等同于
x += 1;
// 建议自增(++)和自减(--)运算符尽量使用+=和-=代替
// witch...case 结构
// switch...case结构要求,在每一个case的最后一行必须是break语句
// 否则会接着运行下一个case。这样不仅容易忘记,还会造成代码的冗长
// 而且,switch...case不使用大括号,不利于代码形式的统一
// 此外,这种结构类似于goto语句,容易造成程序流程的混乱,使得代码结构混乱不堪
// 不符合面向对象编程的原则
function doAction(action) {
switch (action) {
case 'hack':
return 'hack';
case 'slash':
return 'slash';
case 'run':
return 'run';
default:
throw new Error('Invalid action.');
}
}
// 上面的代码建议改写成对象结构
function doAction(action) {
var actions = {
'hack': function () {
return 'hack';
},
'slash': function () {
return 'slash';
},
'run': function () {
return 'run';
}
};
if (typeof actions[action] !== 'function') {
throw new Error('Invalid action.');
}
return actions[action]();
}
// 因此,建议switch...case结构可以用对象结构代替
4. console 对象与控制台
console 对象
- console对象是 JavaScript 的原生对象
- 可以输出各种信息到控制台,并且还提供了很多有用的辅助方法
- console的常见用途有两个
- 调试程序,显示网页代码运行时的错误信息
- 提供了一个命令行接口,用来与网页代码互动
console.log(),console.info(),console.debug()
- console.log方法用于在控制台输出信息。它可以接受一个或多个参数,将它们连接起来输出。
console.log('Hello World')
// Hello World
console.log('a', 'b', 'c')
// a b c
// console.log方法会自动在每次输出的结尾,添加换行符
console.log(1);
console.log(2);
console.log(3);
// 1
// 2
// 3
// 如果第一个参数是格式字符串(使用了格式占位符)
// console.log方法将依次用后面的参数替换占位符,然后再进行输出
console.log(' %s + %s = %s', 1, 1, 2)
// 1 + 1 = 2
// console.log方法支持以下占位符,不同类型的数据必须使用对应的占位符
%s 字符串
%d 整数
%i 整数
%f 浮点数
%o 对象的链接
%c CSS 格式字符串
// 第二个参数是数值,对应的占位符是%d,第三个参数是字符串,对应的占位符是%s
var number = 11 * 9;
var color = 'red';
console.log('%d %s balloons', number, color);
// 99 red balloons
// 使用%c占位符时,对应的参数必须是 CSS 代码,用来对输出内容进行 CSS 渲染
console.log(
'%cThis text is styled!',
'color: red; background: yellow; font-size: 24px;'
)
console.log(' %s + %s ', 1, 1, '= 2')
// 1 + 1 = 2
console.log({foo: 'bar'})
// Object {foo: "bar"}
console.log(Date)
// function Date() { [native code] }
// console.info是console.log方法的别名,用法完全一样
// console.debug方法与console.log方法类似,会在控制台输出调试信息
// 但是,默认情况下,console.debug输出的信息不会显示
// 只有在打开显示级别在verbose的情况下,才会显示
// console对象的所有方法,都可以被覆盖。因此,可以按照自己的需要,定义console.log方法
['log', 'info', 'warn', 'error'].forEach(function(method) {
console[method] = console[method].bind(
console,
new Date().toISOString()
);
});
console.log("出错了!");
// 2014-05-18T09:00.000Z 出错了!
console.warn(),console.error()
- warn方法和error方法也是在控制台输出信息,它们与log方法的不同之处在于
- warn方法输出信息时,在最前面加一个黄色三角,表示警告
- error方法输出信息时,在最前面加一个红色的叉,表示出错。
console.error('Error: %s (%i)', 'Server is not responding', 500)
// Error: Server is not responding (500)
console.warn('Warning! Too few nodes (%d)', document.childNodes.length)
// Warning! Too few nodes (1)
console.table()
- 对于某些复合类型的数据,console.table方法可以将其转为表格显示
var languages = [
{ name: "JavaScript", fileExtension: ".js" },
{ name: "TypeScript", fileExtension: ".ts" },
{ name: "CoffeeScript", fileExtension: ".coffee" }
];
console.table(languages);
var languages = {
csharp: { name: "C#", paradigm: "object-oriented" },
fsharp: { name: "F#", paradigm: "functional" }
};
console.table(languages);
console.count()
- count方法用于计数,输出它被调用了多少次
function greet(user) {
console.count();
return 'hi ' + user;
}
greet('bob')
// : 1
// "hi bob"
greet('alice')
// : 2
// "hi alice"
greet('bob')
// : 3
// "hi bob"
// 该方法可以接受一个字符串作为参数,作为标签,对执行次数进行分类
function greet(user) {
console.count(user);
return "hi " + user;
}
greet('bob')
// bob: 1
// "hi bob"
greet('alice')
// alice: 1
// "hi alice"
greet('bob')
// bob: 2
// "hi bob"
console.dir(),console.dirxml()
- dir方法用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示
- 该方法对于输出 DOM 对象非常有用,因为会显示 DOM 对象的所有属性
- dirxml方法主要用于以目录树的形式,显示 DOM 节点
- 如果参数不是 DOM 节点,而是普通的 JavaScript 对象,console.dirxml等同于console.dir
console.log({f1: 'foo', f2: 'bar'})
// Object {f1: "foo", f2: "bar"}
console.dir({f1: 'foo', f2: 'bar'})
// Object
// f1: "foo"
// f2: "bar"
// __proto__: Object
console.dir(document.body)
console.dir(obj, {colors: true})
console.dirxml(document.body)
console.assert()
- console.assert方法主要用于程序运行过程中,进行条件判断
- 如果不满足条件,就显示一个错误,但不会中断程序执行。这样就相当于提示用户,内部状态不正确
console.assert(false, '判断条件不成立')
// Assertion failed: 判断条件不成立
// 相当于
try {
if (!false) {
throw new Error('判断条件不成立');
}
} catch(e) {
console.error(e);
}
console.time(),console.timeEnd()
- 这两个方法用于计时,可以算出一个操作所花费的准确时间
// time方法表示计时开始,timeEnd方法表示计时结束
// 它们的参数是计时器的名称
// 调用timeEnd方法之后,控制台会显示“计时器名称: 所耗费的时间”
console.time('Array initialize');
var array= new Array(1000000);
for (var i = array.length - 1; i >= 0; i--) {
array[i] = new Object();
};
console.timeEnd('Array initialize');
// Array initialize: 1914.481ms
console.group(),console.groupEnd(),console.groupCollapsed()
- console.group和console.groupEnd这两个方法用于将显示的信息分组
- 它只在输出大量信息时有用,分在一组的信息,可以用鼠标折叠/展开
console.group('一级分组');
console.log('一级分组的内容');
console.group('二级分组');
console.log('二级分组的内容');
console.groupEnd(); // 二级分组结束
console.groupEnd(); // 一级分组结束
// console.groupCollapsed方法与console.group方法很类似
// 唯一的区别是该组的内容,在第一次显示时是收起的(collapsed),而不是展开的
console.trace(),console.clear()
- console.trace方法显示当前执行的代码在堆栈中的调用路径
- console.clear方法用于清除当前控制台的所有输出,将光标回置到第一行
- 如果用户选中了控制台的“Preserve log”选项,console.clear方法将不起作用
console.trace()
// console.trace()
// (anonymous function)
// InjectedScript._evaluateOn
// InjectedScript._evaluateAndWrap
// InjectedScript.evaluate
console.clear()
debugger 语句
- debugger语句主要用于除错,作用是设置断点
- 如果有正在运行的除错工具,程序运行到debugger语句时会自动停下
- 如果没有除错工具,debugger语句不会产生任何结果,JavaScript 引擎自动跳过这一句
for(var i = 0; i < 5; i++){
console.log(i);
if (i === 2) debugger;
}
// 上面代码打印出0,1,2以后,就会暂停,自动打开源码界面,等待进一步处理
三、章节链接
- https://wangdoc.com/javascript/operators/arithmetic.html
- https://wangdoc.com/javascript/operators/comparison.html
- https://wangdoc.com/javascript/operators/boolean.html
- https://wangdoc.com/javascript/operators/bit.html
- https://wangdoc.com/javascript/operators/priority.html
- https://wangdoc.com/javascript/features/conversion.html
- https://wangdoc.com/javascript/features/error.html
- https://wangdoc.com/javascript/features/style.html
- https://wangdoc.com/javascript/features/console.html
「@浪里淘沙的小法师」