一、运算符
1. 算数运算符
10 个算数运算符
加法运算符:x + y减法运算符: x - y乘法运算符: x * y除法运算符:x / y指数运算符:x ** y余数运算符:x % y自增运算符:++x 或者 x++自减运算符:--x 或者 x--数值运算符: +x负数值运算符:-x
加法运算符
- 加法运算符是在运行时决定到底是执行相加,还是执行连接
- 运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)
- 除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载
- 它们的规则是:所有运算子一律转为数值,再进行相应的数学运算
// 数值相加1 + 1 // 2true + true // 21 + true // 2// 遇到字符串会执行连接'a' + 'bc' // "abc"1 + 'a' // "1a"false + 'a' // "falsea"'3' + 4 + 5 // "345"3 + 4 + '5' // "75"1 - '2' // -11 * '2' // 21 / '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方法,得到想要的结果// 自定义 vuLueOfvar obj = {valueOf: function () {return 1;}};obj + 2 // 3// 自定义 toStringvar 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 // -11 % -2 // 1// 为了得到负数的正确余数值,可以先使用绝对值函数function isOdd(n) {return Math.abs(n % 2) === 1;}isOdd(-5) // trueisOdd(-4) // false
自增和自减运算符
var x = 1;++x // 2x // 2--x // 1x // 1var x = 1;var y = 1;x++ // 1++y // 2
数值运算符,负数值运算符
- 数值运算符的作用在于可以将任何值转为数值(与Number函数的作用相同)
- 负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反
- 连用两个负数值运算符,等同于数值运算符
- 数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值
+true // 1+[] // 0+{} // NaNvar x = 1;-x // -1-(-x) // 1
指数运算符
- 注意,指数运算符是右结合,而不是左结合
- 即多个指数运算符连用时,先进行最右边的计算
2 ** 4 // 16// 相当于 2 ** (3 ** 2)2 ** 3 ** 2 // 512
赋值运算符
// 将 1 赋值给变量 xvar x = 1;// 将变量 y 的值赋值给变量 xvar x = y;// 等同于 x = x + yx += y// 等同于 x = x - yx -= y// 等同于 x = x * yx *= y// 等同于 x = x / yx /= y// 等同于 x = x % yx %= y// 等同于 x = x ** yx **= y// 等同于 x = x >> yx >>= y// 等同于 x = x << yx <<= y// 等同于 x = x >>> yx >>>= y// 等同于 x = x & yx &= y// 等同于 x = x | yx |= y// 等同于 x = x ^ yx ^= y
2. 比较运算符
8 个比较运算符
- 相等比较
- 非相等比较,看是否都是字符串
- 是,按照字典顺序比较(比较 Unicode 码)
- 不是,转成数值,再比较数值的大小
> 大于运算符< 小于运算符<= 小于或等于运算符>= 大于或等于运算符== 相等运算符=== 严格相等运算符!= 不相等运算符!== 严格不相等运算符
非相等运算符:字符串的比较
'cat' > 'dog' // false'cat' > 'catalog' // false'cat' > 'Cat' // true''大' > '小' // false
非相等运算符:非字符串的比较
- 如果两个运算子都是原始类型的值,则是先转成数值再比较
- 如果运算子是对象,会转为原始类型的值,再进行比较
// 原始类型值5 > '4' // true// 等同于 5 > Number('4')// 即 5 > 4true > false // true// 等同于 Number(true) > Number(false)// 即 1 > 02 > true // true// 等同于 2 > Number(true)// 即 2 > 1// 任何值(包括NaN本身)与NaN比较,返回的都是false1 > NaN // false1 <= NaN // false'1' > NaN // false'1' <= NaN // falseNaN > NaN // falseNaN <= 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" // falsetrue === "true" // false1 === 0x1 // trueNaN === NaN // false+0 === -0 // true// 两个复合类型(对象、数组、函数)的数据比较时// 不是比较它们的值是否相等,而是比较它们是否指向同一个地址{} === {} // false[] === [] // false(function () {} === function () {}) // false// 如果两个变量引用同一个对象,则它们相等var v1 = {};var v2 = v1;v1 === v2 // true// 对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值var obj1 = {};var obj2 = {};obj1 > obj2 // falseobj1 < obj2 // falseobj1 === obj2 // false// undefined 和 nullundefined === undefined // truenull === null // truevar 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) // 0x // 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) // truex // 1false || 0 || '' || 4 || 'foo' || true// 4false || 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 // undefinedvoid(0) // undefined// 建议采用后一种形式,即总是使用圆括号// 因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果// 比如,void 4 + 7实际上等同于(void 4) + 7// 小例子var x = 3;void (x = 5) //undefinedx // 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 // 1y // 10var 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// 字符串:如果不可以被解析为数值,返回 NaNNumber('324abc') // NaN// 空字符串转为0Number('') // 0// 布尔值:true 转成 1,false 转成 0Number(true) // 1Number(false) // 0// undefined:转成 NaNNumber(undefined) // NaN// null:转成0Number(null) // 0// Number函数将字符串转为数值,要比parseInt函数严格很多// 基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaNparseInt('42 cats') // 42Number('42 cats') // NaN另外,parseInt和Number函数都会自动过滤一个字符串前导和后缀的空格parseInt('\t\v\r12.34\n') // 12Number('\t\v\r12.34\n') // 12.34// 对象// 简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组Number({a: 1}) // NaNNumber([1, 2, 3]) // NaNNumber([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({}) // NaNvar obj = {valueOf: function () {return {};},toString: function () {return {};}};Number(obj)// TypeError: Cannot convert object to primitive valueNumber({valueOf: function () {return 2;}})// 2Number({toString: function () {return 3;}})// 3Number({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) // falseBoolean(null) // falseBoolean(0) // falseBoolean(NaN) // falseBoolean('') // falseBoolean(true) // trueBoolean(false) // falseBoolean({}) // trueBoolean([]) // trueBoolean(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' // 10true - 1 // 0false - 1 // -1'1' - 1 // 0'5' * [] // 0false / '5' // 0'abc' - 1 // NaNnull + 1 // 1undefined + 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 funcvar 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// 222catch代码块之中,还可以再抛出错误,甚至使用嵌套的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()// 0count// 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; // 这句会覆盖掉前面那句 returnconsole.log(4); // 不会运行}console.log(5); // 不会运行}var result = f();// 0// 1// 3result// 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 == ''// true1 == true // true2 == true // false0 == '0' // truefalse == 'false' // falsefalse == '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 Worldconsole.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,第三个参数是字符串,对应的占位符是%svar 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 = 2console.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__: Objectconsole.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.evaluateconsole.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
「@浪里淘沙的小法师」
