一、运算符

1. 算数运算符

10 个算数运算符

  1. 加法运算符:x + y
  2. 减法运算符: x - y
  3. 乘法运算符: x * y
  4. 除法运算符:x / y
  5. 指数运算符:x ** y
  6. 余数运算符:x % y
  7. 自增运算符:++x 或者 x++
  8. 自减运算符:--x 或者 x--
  9. 数值运算符: +x
  10. 负数值运算符:-x

加法运算符

  • 加法运算符是在运行时决定到底是执行相加,还是执行连接
  • 运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)
  • 除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载
  • 它们的规则是:所有运算子一律转为数值,再进行相应的数学运算
  1. // 数值相加
  2. 1 + 1 // 2
  3. true + true // 2
  4. 1 + true // 2
  5. // 遇到字符串会执行连接
  6. 'a' + 'bc' // "abc"
  7. 1 + 'a' // "1a"
  8. false + 'a' // "falsea"
  9. '3' + 4 + 5 // "345"
  10. 3 + 4 + '5' // "75"
  11. 1 - '2' // -1
  12. 1 * '2' // 2
  13. 1 / '2' // 0.5
  14. // 如果运算子是对象,必须先转成原始类型的值,然后再相加
  15. var obj = { p: 1 };
  16. obj + 2 // "[object Object]2"
  17. // JavaScript 自动调用对象的valueOf方法,再自动调用对象的toString方法
  18. var obj = { p: 1 };
  19. obj.valueOf().toString() // "[object Object]"
  20. // 知道了这个规则以后,就可以自己定义valueOf方法或toString方法,得到想要的结果
  21. // 自定义 vuLueOf
  22. var obj = {
  23. valueOf: function () {
  24. return 1;
  25. }
  26. };
  27. obj + 2 // 3
  28. // 自定义 toString
  29. var obj = {
  30. toString: function () {
  31. return 'hello';
  32. }
  33. };
  34. obj + 2 // "hello2"
  35. // 特例,如果运算子是一个Date对象的实例,那么会优先执行toString方法
  36. var obj = new Date();
  37. obj.valueOf = function () { return 1 };
  38. obj.toString = function () { return 'hello' };
  39. obj + 2 // "hello2"
  40. // 此刻 toString 方法优先执行

余数运算符

  • 运算结果的正负号由第一个运算子的正负号决定
  1. 12 % 5 // 2
  2. -1 % 2 // -1
  3. 1 % -2 // 1
  4. // 为了得到负数的正确余数值,可以先使用绝对值函数
  5. function isOdd(n) {
  6. return Math.abs(n % 2) === 1;
  7. }
  8. isOdd(-5) // true
  9. isOdd(-4) // false

自增和自减运算符

  1. var x = 1;
  2. ++x // 2
  3. x // 2
  4. --x // 1
  5. x // 1
  6. var x = 1;
  7. var y = 1;
  8. x++ // 1
  9. ++y // 2

数值运算符,负数值运算符

  • 数值运算符的作用在于可以将任何值转为数值(与Number函数的作用相同)
  • 负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反
  • 连用两个负数值运算符,等同于数值运算符
  • 数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值
  1. +true // 1
  2. +[] // 0
  3. +{} // NaN
  4. var x = 1;
  5. -x // -1
  6. -(-x) // 1

指数运算符

  • 注意,指数运算符是右结合,而不是左结合
  • 即多个指数运算符连用时,先进行最右边的计算
  1. 2 ** 4 // 16
  2. // 相当于 2 ** (3 ** 2)
  3. 2 ** 3 ** 2 // 512

赋值运算符

  1. // 将 1 赋值给变量 x
  2. var x = 1;
  3. // 将变量 y 的值赋值给变量 x
  4. var x = y;
  5. // 等同于 x = x + y
  6. x += y
  7. // 等同于 x = x - y
  8. x -= y
  9. // 等同于 x = x * y
  10. x *= y
  11. // 等同于 x = x / y
  12. x /= y
  13. // 等同于 x = x % y
  14. x %= y
  15. // 等同于 x = x ** y
  16. x **= y
  17. // 等同于 x = x >> y
  18. x >>= y
  19. // 等同于 x = x << y
  20. x <<= y
  21. // 等同于 x = x >>> y
  22. x >>>= y
  23. // 等同于 x = x & y
  24. x &= y
  25. // 等同于 x = x | y
  26. x |= y
  27. // 等同于 x = x ^ y
  28. x ^= y

2. 比较运算符

8 个比较运算符

  • 相等比较
  • 非相等比较,看是否都是字符串
    1. 是,按照字典顺序比较(比较 Unicode 码)
    2. 不是,转成数值,再比较数值的大小
  1. > 大于运算符
  2. < 小于运算符
  3. <= 小于或等于运算符
  4. >= 大于或等于运算符
  5. == 相等运算符
  6. === 严格相等运算符
  7. != 不相等运算符
  8. !== 严格不相等运算符

非相等运算符:字符串的比较

  1. 'cat' > 'dog' // false
  2. 'cat' > 'catalog' // false
  3. 'cat' > 'Cat' // true'
  4. '大' > '小' // false

非相等运算符:非字符串的比较

  • 如果两个运算子都是原始类型的值,则是先转成数值再比较
  • 如果运算子是对象,会转为原始类型的值,再进行比较
  1. // 原始类型值
  2. 5 > '4' // true
  3. // 等同于 5 > Number('4')
  4. // 即 5 > 4
  5. true > false // true
  6. // 等同于 Number(true) > Number(false)
  7. // 即 1 > 0
  8. 2 > true // true
  9. // 等同于 2 > Number(true)
  10. // 即 2 > 1
  11. // 任何值(包括NaN本身)与NaN比较,返回的都是false
  12. 1 > NaN // false
  13. 1 <= NaN // false
  14. '1' > NaN // false
  15. '1' <= NaN // false
  16. NaN > NaN // false
  17. NaN <= NaN // false
  18. // 对象
  19. var x = [2];
  20. x > '11' // true
  21. // 等同于 [2].valueOf().toString() > '11'
  22. // 即 '2' > '11'
  23. // 如果运算子是对象,会转为原始类型的值,再进行比较
  24. // 先调用valueOf方法;如果返回的还是对象,再接着调用toString方法
  25. x.valueOf = function () { return '1' };
  26. x > '11' // false
  27. // 等同于 [2].valueOf() > '11'
  28. // 即 '1' > '11'
  29. // 两个对象的比较也是如此
  30. [2] > [1] // true
  31. // 等同于 [2].valueOf().toString() > [1].valueOf().toString()
  32. // 即 '2' > '1'
  33. [2] > [11] // true
  34. // 等同于 [2].valueOf().toString() > [11].valueOf().toString()
  35. // 即 '2' > '11'
  36. { x: 2 } >= { x: 1 } // true
  37. // 等同于 { x: 2 }.valueOf().toString() >= { x: 1 }.valueOf().toString()
  38. // 即 '[object Object]' >= '[object Object]'

严格相等运算符

  • JavaScript 提供两种相等运算符:==和===
  1. 1 === "1" // false
  2. true === "true" // false
  3. 1 === 0x1 // true
  4. NaN === NaN // false
  5. +0 === -0 // true
  6. // 两个复合类型(对象、数组、函数)的数据比较时
  7. // 不是比较它们的值是否相等,而是比较它们是否指向同一个地址
  8. {} === {} // false
  9. [] === [] // false
  10. (function () {} === function () {}) // false
  11. // 如果两个变量引用同一个对象,则它们相等
  12. var v1 = {};
  13. var v2 = v1;
  14. v1 === v2 // true
  15. // 对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值
  16. var obj1 = {};
  17. var obj2 = {};
  18. obj1 > obj2 // false
  19. obj1 < obj2 // false
  20. obj1 === obj2 // false
  21. // undefined 和 null
  22. undefined === undefined // true
  23. null === null // true
  24. var v1;
  25. var v2;
  26. v1 === v2 // true

严格不相等运算符

  • 它的算法就是先求严格相等运算符的结果,然后返回相反值
    1. 1 !== '1' // true
    2. // 等同于
    3. !(1 === '1')

相等运算符和不相等运算符

  • 建议不使用

3. 布尔运算符

4 个布尔运算符

  1. 取反运算符:!
  2. 且运算符:&&
  3. 或运算符:||
  4. 三元运算符:?:

取反运算符(!)

  1. !true // false
  2. !false // true
  3. !undefined // true
  4. !null // true
  5. !0 // true
  6. !NaN // true
  7. !"" // true
  8. !54 // false
  9. !'hello' // false
  10. ![] // false
  11. !{} // false
  12. // 如果对一个值连续做两次取反运算,等于将其转为对应的布尔值
  13. // 与Boolean函数的作用相同。这是一种常用的类型转换的写法
  14. !!x
  15. // 等同于
  16. Boolean(x)

且运算符

  • 且运算符(&&)往往用于多个表达式的求值
  • 它的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值)
  • 如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值
  1. 't' && '' // ""
  2. 't' && 'f' // "f"
  3. 't' && (1 + 2) // 3
  4. '' && 'f' // ""
  5. '' && '' // ""
  6. var x = 1;
  7. (1 - 1) && ( x += 1) // 0
  8. x // 1
  9. // 这种跳过第二个运算子的机制,被称为“短路”
  10. if (i) {
  11. doSomething();
  12. }
  13. // 等价于
  14. i && doSomething();
  15. true && 'foo' && '' && 4 && 'foo' && true
  16. // ''
  17. 1 && 2 && 3
  18. // 3

或运算符(||)

  1. 't' || '' // "t"
  2. 't' || 'f' // "t"
  3. '' || 'f' // "f"
  4. '' || '' // ""
  5. // 短路规则对这个运算符也适用
  6. var x = 1;
  7. true || (x = 2) // true
  8. x // 1
  9. false || 0 || '' || 4 || 'foo' || true
  10. // 4
  11. false || 0 || ''
  12. // ''
  13. function saveText(text) {
  14. text = text || '';
  15. // ...
  16. }
  17. // 或者写成
  18. saveText(this.text || '')
  19. // 或运算符常用于为一个变量设置默认值
  20. function saveText(text) {
  21. text = text || '';
  22. // ...
  23. }
  24. // 或者写成
  25. saveText(this.text || '')

三元条件运算符(?:)

  1. 't' ? 'hello' : 'world' // "hello"
  2. 0 ? 'hello' : 'world' // "world"
  3. console.log(true ? 'T' : 'F');

4. 二进制位运算符

7 个二进制位运算符

  • 这些位运算符直接处理每一个比特位(bit)
    • 好处是速度极快,缺点是很不直观
  • 位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行
  • 许多场合不能使用它们,否则会使代码难以理解和查错
  1. 二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1
  2. 二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0
  3. 二进制否运算符(not):符号为~,表示对一个二进制位取反
  4. 异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0
  5. 左移运算符(left shift):符号为<<,详见下文解释
  6. 右移运算符(right shift):符号为>>,详见下文解释
  7. 头部补零的右移运算符(zero filled right shift):符号为>>>,详见下文解释

5. 其他运算符,运算顺序

void 运算符

  • 作用是执行一个表达式,然后不返回任何值,或者说返回undefined
  • 这个运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超级链接中插入代码防止网页跳转
  1. void 0 // undefined
  2. void(0) // undefined
  3. // 建议采用后一种形式,即总是使用圆括号
  4. // 因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果
  5. // 比如,void 4 + 7实际上等同于(void 4) + 7
  6. // 小例子
  7. var x = 3;
  8. void (x = 5) //undefined
  9. x // 5
  10. <a href="http://example.com" onclick="f(); return false;">点击</a>
  11. // 换成以下
  12. <a href="javascript: void(f())">文字</a>
  13. // 用户点击链接提交表单,但是不产生页面跳转
  14. <a href="javascript: void(document.form.submit())">
  15. 提交
  16. </a>

逗号运算符

  • 用于对两个表达式求值,并返回后一个表达式的值
  • 逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作
  1. 'a', 'b' // "b"
  2. var x = 0;
  3. var y = (x++, 10);
  4. x // 1
  5. y // 10
  6. var value = (console.log('Hi!'), true);
  7. // Hi!
  8. value // true

优先级

  • 记住所有运算符的优先级,是非常难的,也是没有必要的
  • 圆括号的优先级是最高的,即圆括号中的表达式会第一个运算
  • 顺便说一下,圆括号不是运算符,而是一种语法结构,一共有两种用法
    • 一种是把表达式放在圆括号之中,提升运算的优先级
    • 另一种是跟在函数的后面,作用是调用函数
  • 因为圆括号不是运算符,所以不具有求值作用,只改变运算的优先级
  1. // 该代码可以运行,这验证了圆括号只改变优先级,不会求值
  2. var x = 1;
  3. (x) = 2;
  4. (expression)
  5. // 等同于
  6. expression
  7. // 函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数
  8. function f() {
  9. return 1;
  10. }
  11. (f) // function f(){return 1;}
  12. f() // 1
  13. // 圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错
  14. (var a = 1)
  15. // SyntaxError: Unexpected token var

左结合与右结合

  • 对于优先级别相同的运算符,同时出现的时候,就会有计算顺序的问题
  1. // 左结合
  2. x + y + z
  3. // JS 引擎解释如下
  4. (x + y) + z
  5. // 右结合
  6. w = x = y = z;
  7. q = a ? b : c ? d : e ? f : g;
  8. // JS 引擎解释如下
  9. w = (x = (y = z));
  10. q = a ? b : (c ? d : (e ? f : g));
  11. // 指数运算符(**)也是右结合
  12. 2 ** 3 ** 2
  13. // 相当于 2 ** (3 ** 2)
  14. // 512

二、语法专题

1. 数据类型转换

  • JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值
  • 虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的
  • 如果运算符发现,运算子的类型与预期不符,就会自动转换类型
  1. var x = y ? 1 : 'a';
  2. // 减法运算符预期左右两侧的运算子应该是数值
  3. '4' - '3' // 1

强制转换

  • Number()
  • String()
  • Boolean()

Number()

  • 使用Number函数,可以将任意类型的值转化成数值,分成两种情况讨论
    • 一种是参数是原始类型的值
    • 另一种是参数是对象
  1. // 原始类型值
  2. // 数值:转换后还是原来的值
  3. Number(324) // 324
  4. // 字符串:如果可以被解析为数值,则转换为相应的数值
  5. Number('324') // 324
  6. // 字符串:如果不可以被解析为数值,返回 NaN
  7. Number('324abc') // NaN
  8. // 空字符串转为0
  9. Number('') // 0
  10. // 布尔值:true 转成 1,false 转成 0
  11. Number(true) // 1
  12. Number(false) // 0
  13. // undefined:转成 NaN
  14. Number(undefined) // NaN
  15. // null:转成0
  16. Number(null) // 0
  17. // Number函数将字符串转为数值,要比parseInt函数严格很多
  18. // 基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN
  19. parseInt('42 cats') // 42
  20. Number('42 cats') // NaN
  21. 另外,parseIntNumber函数都会自动过滤一个字符串前导和后缀的空格
  22. parseInt('\t\v\r12.34\n') // 12
  23. Number('\t\v\r12.34\n') // 12.34
  24. // 对象
  25. // 简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组
  26. Number({a: 1}) // NaN
  27. Number([1, 2, 3]) // NaN
  28. Number([5]) // 5.
  29. // 第一步,调用对象自身的valueOf方法,如果返回原始类型的值,则直接对该值使用Number函数
  30. // 第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法
  31. // 第三步,如果toString方法返回原始类型的值,则对该值使用Number函数
  32. // 第四步,如果toString方法返回的是对象,就报错
  33. var obj = {x: 1};
  34. Number(obj) // NaN
  35. // 等同于
  36. if (typeof obj.valueOf() === 'object') {
  37. Number(obj.toString());
  38. } else {
  39. Number(obj.valueOf());
  40. }
  41. Number({}) // NaN
  42. var obj = {
  43. valueOf: function () {
  44. return {};
  45. },
  46. toString: function () {
  47. return {};
  48. }
  49. };
  50. Number(obj)
  51. // TypeError: Cannot convert object to primitive value
  52. Number({
  53. valueOf: function () {
  54. return 2;
  55. }
  56. })
  57. // 2
  58. Number({
  59. toString: function () {
  60. return 3;
  61. }
  62. })
  63. // 3
  64. Number({
  65. valueOf: function () {
  66. return 2;
  67. },
  68. toString: function () {
  69. return 3;
  70. }
  71. })
  72. // 2

String()

  • 可以将任意类型的值转化成字符串
  1. // 原始数据类型
  2. String(123) // "123"
  3. String('abc') // "abc"
  4. String(true) // "true"
  5. String(undefined) // "undefined"
  6. String(null) // "null"
  7. // 对象
  8. String({a: 1}) // "[object Object]"
  9. String([1, 2, 3]) // "1,2,3"
  10. // 第一步,先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数
  11. // 如果toString方法返回的是对象,再调用原对象的valueOf方法
  12. // 如果valueOf方法返回原始类型的值,则对该值使用String函数
  13. // 如果valueOf方法返回的是对象,就报错
  14. String({a: 1})
  15. // "[object Object]"
  16. // 等同于
  17. String({a: 1}.toString())
  18. // "[object Object]"
  19. // 如果toString法和valueOf方法,返回的都是对象,就会报错
  20. var obj = {
  21. valueOf: function () {
  22. return {};
  23. },
  24. toString: function () {
  25. return {};
  26. }
  27. };
  28. String(obj)
  29. // TypeError: Cannot convert object to primitive value
  30. // 自定义toString方法,改变返回值的例子
  31. String({
  32. toString: function () {
  33. return 3;
  34. }
  35. })
  36. // "3"
  37. String({
  38. valueOf: function () {
  39. return 2;
  40. }
  41. })
  42. // "[object Object]"
  43. String({
  44. valueOf: function () {
  45. return 2;
  46. },
  47. toString: function () {
  48. return 3;
  49. }
  50. })
  51. // "3"

Boolean()

  • 可以将任意类型的值转为布尔值
  1. // 除了以下 5 个 falsy 值,其他全为 true
  2. // undefined null 0 NaN ''
  3. Boolean(undefined) // false
  4. Boolean(null) // false
  5. Boolean(0) // false
  6. Boolean(NaN) // false
  7. Boolean('') // false
  8. Boolean(true) // true
  9. Boolean(false) // false
  10. Boolean({}) // true
  11. Boolean([]) // true
  12. Boolean(new Boolean(false)) // true

自动转换

  • 预期什么类型的值,就调用该类型的转换函数
  • 由于自动转换具有不确定性,而且不易除错
  • 建议在预期为布尔值、数值、字符串的地方
  • 全部使用Boolean、Number和String函数进行显式转换
  1. 123 + 'abc' // "123abc"
  2. if ('abc') {
  3. console.log('hello')
  4. } // "hello"
  5. + {foo: 'bar'} // NaN
  6. - [1, 2, 3] // NaN
  7. // 自动转换为布尔值
  8. // JavaScript 遇到预期为布尔值的地方(比如if语句的条件部分)
  9. // 就会将非布尔值的参数自动转换为布尔值
  10. // 系统内部会自动调用Boolean函数
  11. if ( !undefined
  12. && !null
  13. && !0
  14. && !NaN
  15. && !''
  16. ) {
  17. console.log('true');
  18. } // true
  19. // 将一个表达式转为布尔值
  20. // 写法一
  21. expression ? true : false
  22. // 写法二
  23. !! expression
  24. // 自动转换为字符串
  25. // 遇到预期为字符串的地方,就会将非字符串的值自动转为字符串
  26. // 先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串
  27. '5' + 1 // '51'
  28. '5' + true // "5true"
  29. '5' + false // "5false"
  30. '5' + {} // "5[object Object]"
  31. '5' + [] // "5"
  32. '5' + function (){} // "5function (){}"
  33. '5' + undefined // "5undefined"
  34. '5' + null // "5null"
  35. // 这种自动转换很容易出错
  36. var obj = {
  37. width: '100'
  38. };
  39. obj.width + 20 // "10020",开发者可能期望 120
  40. // 自动转换为数值
  41. //遇到预期为数值的地方,就会将参数值自动转换为数值
  42. // 系统内部会自动调用Number函数
  43. // 除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值
  44. '5' - '2' // 3
  45. '5' * '2' // 10
  46. true - 1 // 0
  47. false - 1 // -1
  48. '1' - 1 // 0
  49. '5' * [] // 0
  50. false / '5' // 0
  51. 'abc' - 1 // NaN
  52. null + 1 // 1
  53. undefined + 1 // NaN
  54. // 注意:null转为数值时为0,而undefined转为数值时为NaN
  55. // 一元运算符也会把运算子转成数值
  56. +'abc' // NaN
  57. -'abc' // NaN
  58. +true // 1
  59. -false // 0

2. 错误处理机制

Error 实例对象

  1. // message:错误提示信息
  2. var err = new Error('出错了');
  3. err.message // "出错了"
  4. // name:错误名称(非标准属性)
  5. if (error.name) {
  6. console.log(error.name + ': ' + error.message);
  7. }
  8. // stack属性用来查看错误发生时的堆栈。
  9. function throwit() {
  10. throw new Error('');
  11. }
  12. function catchit() {
  13. try {
  14. throwit();
  15. } catch(e) {
  16. console.log(e.stack); // print stack trace
  17. }
  18. }
  19. catchit()
  20. // Error
  21. // at throwit (~/examples/throwcatch.js:9:11)
  22. // at catchit (~/examples/throwcatch.js:3:9)
  23. // at repl:1:5

原生错误类型

  1. // SyntaxError对象是解析代码时发生的语法错误
  2. // 变量名错误
  3. var 1a;
  4. // Uncaught SyntaxError: Invalid or unexpected token
  5. // 缺少括号
  6. console.log 'hello');
  7. // Uncaught SyntaxError: Unexpected string
  8. // ReferenceError对象是引用一个不存在的变量时发生的错误
  9. // 使用一个不存在的变量
  10. unknownVariable
  11. // Uncaught ReferenceError: unknownVariable is not defined
  12. // 等号左侧不是变量
  13. console.log() = 1
  14. // Uncaught ReferenceError: Invalid left-hand side in assignment
  15. // RangeError对象是一个值超出有效范围时发生的错误
  16. // 主要有几种情况,一是数组长度为负数
  17. // 二是Number对象的方法参数超出范围,以及函数堆栈超过最大值
  18. // 数组长度不得为负数
  19. new Array(-1)
  20. // Uncaught RangeError: Invalid array length
  21. // TypeError对象是变量或参数不是预期类型时发生的错误
  22. new 123
  23. // Uncaught TypeError: number is not a func
  24. var obj = {};
  25. obj.unknownMethod() // obj.unknownMethod的值是undefined,而不是一个函数
  26. // Uncaught TypeError: obj.unknownMethod is not a function
  27. // URIError对象是 URI 相关函数的参数不正确时抛出的错误
  28. // 主要涉及encodeURI()、decodeURI()、encodeURIComponent()
  29. // decodeURIComponent()、escape()和unescape()这六个函数
  30. decodeURI('%2')
  31. // URIError: URI malformed
  32. // EvalError 对象,eval函数没有被正确执行时,会抛出EvalError错误
  33. // 该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留
  34. // 总结
  35. 以上这6种派生错误,连同原始的Error对象,都是构造函数
  36. 开发者可以使用它们,手动生成错误对象的实例
  37. 这些构造函数都接受一个参数,代表错误提示信息(message
  38. var err1 = new Error('出错了!');
  39. var err2 = new RangeError('出错了,变量超出有效范围!');
  40. var err3 = new TypeError('出错了,变量类型无效!');
  41. err1.message // "出错了!"
  42. err2.message // "出错了,变量超出有效范围!"
  43. err3.message // "出错了,变量类型无效!"

自定义错误

  1. // 除了 JavaScript 原生提供的错误对象,还可以定义自己的错误对象
  2. function UserError(message) {
  3. this.message = message || '默认信息';
  4. this.name = 'UserError';
  5. }
  6. UserError.prototype = new Error();
  7. UserError.prototype.constructor = UserError;
  8. // 生成这种自定义类型的错误的实例
  9. new UserError('这是自定义的错误!');

throw 语句

  1. // throw语句的作用是手动中断程序执行,抛出一个错误
  2. if (x <= 0) {
  3. throw new Error('x 必须为正数');
  4. }
  5. // Uncaught ReferenceError: x is not defined
  6. // throw也可以抛出自定义错误
  7. function UserError(message) {
  8. this.message = message || '默认信息';
  9. this.name = 'UserError';
  10. }
  11. throw new UserError('出错了!');
  12. // Uncaught UserError {message: "出错了!", name: "UserError"}
  13. // 对于 JavaScript 引擎来说,遇到throw语句,程序就中止了
  14. // 引擎会接收到throw抛出的信息,可能是一个错误实例,也可能是其他类型的值

try…catch 结构

  1. // 一旦发生错误,程序就中止执行了
  2. // JavaScript 提供了try...catch结构,允许对错误进行处理,选择是否往下执行
  3. try {
  4. throw new Error('出错了!');
  5. } catch (e) {
  6. console.log(e.name + ": " + e.message);
  7. console.log(e.stack);
  8. }
  9. // Error: 出错了!
  10. // at <anonymous>:3:9
  11. // ...
  12. // 常用方法
  13. try {
  14. f();
  15. } catch(e) {
  16. // 处理错误
  17. }
  18. try {
  19. throw "出错了";
  20. } catch (e) {
  21. console.log(111);
  22. }
  23. console.log(222);
  24. // 111
  25. // 222
  26. catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构
  27. var n = 100;
  28. try {
  29. throw n;
  30. } catch (e) {
  31. if (e <= 50) {
  32. // ...
  33. } else {
  34. throw e;
  35. }
  36. }
  37. // Uncaught 100
  38. // 为了捕捉不同类型的错误,catch代码块之中可以加入判断语句
  39. try {
  40. foo.bar();
  41. } catch (e) {
  42. if (e instanceof EvalError) {
  43. console.log(e.name + ": " + e.message);
  44. } else if (e instanceof RangeError) {
  45. console.log(e.name + ": " + e.message);
  46. }
  47. // ...
  48. }

finally 代码块

  1. // try...catch结构允许在最后添加一个finally代码块
  2. // 表示不管是否出现错误,都必需在最后运行的语句
  3. function cleansUp() {
  4. try {
  5. throw new Error('出错了……');
  6. console.log('此行不会执行');
  7. } finally {
  8. console.log('完成清理工作');
  9. }
  10. }
  11. cleansUp()
  12. // 完成清理工作
  13. // Uncaught Error: 出错了……
  14. // at cleansUp (<anonymous>:3:11)
  15. // at <anonymous>:10:1
  16. // 上面代码中,由于没有catch语句块,一旦发生错误,代码就会中断执行
  17. // 中断执行之前,会先执行finally代码块,然后再向用户提示报错信息
  18. var count = 0;
  19. function countUp() {
  20. try {
  21. return count;
  22. } finally {
  23. count++;
  24. }
  25. }
  26. countUp()
  27. // 0
  28. count
  29. // 1
  30. // 上面代码说明,return语句里面的count的值,是在finally代码块运行之前就获取了
  31. // 下面是finally代码块用法的典型场景
  32. openFile();
  33. try {
  34. writeFile(Data);
  35. } catch(e) {
  36. handleError(e);
  37. } finally {
  38. closeFile();
  39. }
  40. // 下面例子充分反映了try...catch...finally这三者之间的执行顺序
  41. function f() {
  42. try {
  43. console.log(0);
  44. throw 'bug';
  45. } catch(e) {
  46. console.log(1);
  47. return true; // 这句原本会延迟到 finally 代码块结束再执行
  48. console.log(2); // 不会运行
  49. } finally {
  50. console.log(3);
  51. return false; // 这句会覆盖掉前面那句 return
  52. console.log(4); // 不会运行
  53. }
  54. console.log(5); // 不会运行
  55. }
  56. var result = f();
  57. // 0
  58. // 1
  59. // 3
  60. result
  61. // false
  62. // catch代码块之中,触发转入finally代码块的标志,不仅有return语句,还有throw语句
  63. function f() {
  64. try {
  65. throw '出错了!';
  66. } catch(e) {
  67. console.log('捕捉到内部错误');
  68. throw e; // 这句原本会等到finally结束再执行
  69. } finally {
  70. return false; // 直接返回
  71. }
  72. }
  73. try {
  74. f();
  75. } catch(e) {
  76. // 此处不会执行
  77. console.log('caught outer "bogus"');
  78. }
  79. // 捕捉到内部错误
  80. // try代码块内部,还可以再使用try代码块
  81. try {
  82. try {
  83. consle.log('Hello world!'); // 报错
  84. }
  85. finally {
  86. console.log('Finally');
  87. }
  88. console.log('Will I run?');
  89. } catch(error) {
  90. console.error(error.message);
  91. }
  92. // Finally
  93. // consle is not defined
  94. // 上面代码中,try里面还有一个try。内层的try报错(console拼错了)
  95. 这时会执行内层的finally代码块,然后抛出错误,被外层的catch捕获

3. 编程风格

  • 编译器的规范叫做“语法规则”(grammar),这是程序员必须遵守的
  • 而编译器忽略的部分,就叫“编程风格”(programming style),这是程序员可以自由选择的
  1. // 缩进
  2. // 不要一会使用 Tab 键,一会使用空格键
  3. // 区块
  4. // 如果循环和判断的代码体只有一行,JavaScript 允许该区块(block)省略大括号
  5. if (a)
  6. b();
  7. c();
  8. // 等同与
  9. if (a) {
  10. b();
  11. }
  12. c();
  13. // JavaScript 要使用起首的大括号跟在关键字的后面
  14. // 因为 JavaScript 会自动添加句末的分号,导致一些难以察觉的错误
  15. block {
  16. // ...
  17. }
  18. // 圆括号
  19. // 圆括号(parentheses)在 JavaScript 中有两种作用
  20. // 一种表示函数的调用,另一种表示表达式的组合
  21. // 圆括号表示函数的调用
  22. console.log('abc');
  23. // 圆括号表示表达式的组合
  24. (1 + 2) * 3
  25. // 建议可以用空格,区分这两种不同的括号
  26. // 表示函数调用时,函数名与左括号之间没有空格。
  27. // 表示函数定义时,函数名与左括号之间没有空格。
  28. // 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。
  29. // 行尾的分号
  30. // 分号表示一条语句的结束, JavaScript 允许省略行尾的分号
  31. // 为了方便使用,我不加分号
  32. // 并额外记忆一些,规则,不要括号开头,不要奇奇怪怪的字符开头
  33. // 大多数情况下,JavaScript 会自动添加分号
  34. var a = 1
  35. // 等同于
  36. var a = 1;
  37. // 全局变量
  38. // JavaScript 最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写
  39. // 因此,建议避免使用全局变量
  40. // 如果不得不使用,可以考虑用大写字母表示变量名
  41. 这样更容易看出这是全局变量,比如UPPER_CASE
  42. // 变量声明
  43. // JavaScript 会自动将变量声明“提升”(hoist)到代码块(block)的头部
  44. if (!x) {
  45. var x = {};
  46. }
  47. // 等同于
  48. var x;
  49. if (!x) {
  50. x = {};
  51. }
  52. // 了避免可能出现的问题,最好把变量声明都放在代码块的头部
  53. // with 语句
  54. with可以减少代码的书写,但是会造成混淆
  55. with (o) {
  56.  foo = bar;
  57. }
  58. // 可能有四种运行结果
  59. o.foo = bar;
  60. o.foo = o.bar;
  61. foo = bar;
  62. foo = o.bar;
  63. // 这四种结果都可能发生,取决于不同的变量是否有定义
  64. // 因此,不要使用with语句
  65. // 相等和严格相等
  66. // 相等运算符会自动转换变量类型,造成很多意想不到的情况
  67. 0 == ''// true
  68. 1 == true // true
  69. 2 == true // false
  70. 0 == '0' // true
  71. false == 'false' // false
  72. false == '0' // true
  73. ' \t\r\n ' == 0 // true
  74. // 建议不要使用相等运算符(==),只使用严格相等运算符(===)
  75. // 语句的合并
  76. a = b;
  77. if (a) {
  78. // ...
  79. }
  80. // 有人喜欢写成这样的
  81. //虽然语句少了一行,但是可读性大打折扣,而且会造成误读
  82. if (a = b) {
  83. // ...
  84. }
  85. // 自增和自减运算符
  86. // 自增(++)和自减(--)运算符,放在变量的前面或后面,返回的值不一样,很容易发生错误
  87. // 事实上,所有的++运算符都可以用+= 1代替
  88. ++x
  89. // 等同于
  90. x += 1;
  91. // 建议自增(++)和自减(--)运算符尽量使用+=和-=代替
  92. // witch...case 结构
  93. // switch...case结构要求,在每一个case的最后一行必须是break语句
  94. // 否则会接着运行下一个case。这样不仅容易忘记,还会造成代码的冗长
  95. // 而且,switch...case不使用大括号,不利于代码形式的统一
  96. // 此外,这种结构类似于goto语句,容易造成程序流程的混乱,使得代码结构混乱不堪
  97. // 不符合面向对象编程的原则
  98. function doAction(action) {
  99. switch (action) {
  100. case 'hack':
  101. return 'hack';
  102. case 'slash':
  103. return 'slash';
  104. case 'run':
  105. return 'run';
  106. default:
  107. throw new Error('Invalid action.');
  108. }
  109. }
  110. // 上面的代码建议改写成对象结构
  111. function doAction(action) {
  112. var actions = {
  113. 'hack': function () {
  114. return 'hack';
  115. },
  116. 'slash': function () {
  117. return 'slash';
  118. },
  119. 'run': function () {
  120. return 'run';
  121. }
  122. };
  123. if (typeof actions[action] !== 'function') {
  124. throw new Error('Invalid action.');
  125. }
  126. return actions[action]();
  127. }
  128. // 因此,建议switch...case结构可以用对象结构代替

4. console 对象与控制台

console 对象

  • console对象是 JavaScript 的原生对象
  • 可以输出各种信息到控制台,并且还提供了很多有用的辅助方法
  • console的常见用途有两个
    • 调试程序,显示网页代码运行时的错误信息
    • 提供了一个命令行接口,用来与网页代码互动

console.log(),console.info(),console.debug()

  • console.log方法用于在控制台输出信息。它可以接受一个或多个参数,将它们连接起来输出。
  1. console.log('Hello World')
  2. // Hello World
  3. console.log('a', 'b', 'c')
  4. // a b c
  5. // console.log方法会自动在每次输出的结尾,添加换行符
  6. console.log(1);
  7. console.log(2);
  8. console.log(3);
  9. // 1
  10. // 2
  11. // 3
  12. // 如果第一个参数是格式字符串(使用了格式占位符)
  13. // console.log方法将依次用后面的参数替换占位符,然后再进行输出
  14. console.log(' %s + %s = %s', 1, 1, 2)
  15. // 1 + 1 = 2
  16. // console.log方法支持以下占位符,不同类型的数据必须使用对应的占位符
  17. %s 字符串
  18. %d 整数
  19. %i 整数
  20. %f 浮点数
  21. %o 对象的链接
  22. %c CSS 格式字符串
  23. // 第二个参数是数值,对应的占位符是%d,第三个参数是字符串,对应的占位符是%s
  24. var number = 11 * 9;
  25. var color = 'red';
  26. console.log('%d %s balloons', number, color);
  27. // 99 red balloons
  28. // 使用%c占位符时,对应的参数必须是 CSS 代码,用来对输出内容进行 CSS 渲染
  29. console.log(
  30. '%cThis text is styled!',
  31. 'color: red; background: yellow; font-size: 24px;'
  32. )
  33. console.log(' %s + %s ', 1, 1, '= 2')
  34. // 1 + 1 = 2
  35. console.log({foo: 'bar'})
  36. // Object {foo: "bar"}
  37. console.log(Date)
  38. // function Date() { [native code] }
  39. // console.info是console.log方法的别名,用法完全一样
  40. // console.debug方法与console.log方法类似,会在控制台输出调试信息
  41. // 但是,默认情况下,console.debug输出的信息不会显示
  42. // 只有在打开显示级别在verbose的情况下,才会显示
  43. // console对象的所有方法,都可以被覆盖。因此,可以按照自己的需要,定义console.log方法
  44. ['log', 'info', 'warn', 'error'].forEach(function(method) {
  45. console[method] = console[method].bind(
  46. console,
  47. new Date().toISOString()
  48. );
  49. });
  50. console.log("出错了!");
  51. // 2014-05-18T09:00.000Z 出错了!

console.warn(),console.error()

  • warn方法和error方法也是在控制台输出信息,它们与log方法的不同之处在于
  • warn方法输出信息时,在最前面加一个黄色三角,表示警告
  • error方法输出信息时,在最前面加一个红色的叉,表示出错。
  1. console.error('Error: %s (%i)', 'Server is not responding', 500)
  2. // Error: Server is not responding (500)
  3. console.warn('Warning! Too few nodes (%d)', document.childNodes.length)
  4. // Warning! Too few nodes (1)

console.table()

  • 对于某些复合类型的数据,console.table方法可以将其转为表格显示
  1. var languages = [
  2. { name: "JavaScript", fileExtension: ".js" },
  3. { name: "TypeScript", fileExtension: ".ts" },
  4. { name: "CoffeeScript", fileExtension: ".coffee" }
  5. ];
  6. console.table(languages);
  7. var languages = {
  8. csharp: { name: "C#", paradigm: "object-oriented" },
  9. fsharp: { name: "F#", paradigm: "functional" }
  10. };
  11. console.table(languages);

console.count()

  • count方法用于计数,输出它被调用了多少次
  1. function greet(user) {
  2. console.count();
  3. return 'hi ' + user;
  4. }
  5. greet('bob')
  6. // : 1
  7. // "hi bob"
  8. greet('alice')
  9. // : 2
  10. // "hi alice"
  11. greet('bob')
  12. // : 3
  13. // "hi bob"
  14. // 该方法可以接受一个字符串作为参数,作为标签,对执行次数进行分类
  15. function greet(user) {
  16. console.count(user);
  17. return "hi " + user;
  18. }
  19. greet('bob')
  20. // bob: 1
  21. // "hi bob"
  22. greet('alice')
  23. // alice: 1
  24. // "hi alice"
  25. greet('bob')
  26. // bob: 2
  27. // "hi bob"

console.dir(),console.dirxml()

  • dir方法用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示
  • 该方法对于输出 DOM 对象非常有用,因为会显示 DOM 对象的所有属性
  • dirxml方法主要用于以目录树的形式,显示 DOM 节点
  • 如果参数不是 DOM 节点,而是普通的 JavaScript 对象,console.dirxml等同于console.dir
  1. console.log({f1: 'foo', f2: 'bar'})
  2. // Object {f1: "foo", f2: "bar"}
  3. console.dir({f1: 'foo', f2: 'bar'})
  4. // Object
  5. // f1: "foo"
  6. // f2: "bar"
  7. // __proto__: Object
  8. console.dir(document.body)
  9. console.dir(obj, {colors: true})
  10. console.dirxml(document.body)

console.assert()

  • console.assert方法主要用于程序运行过程中,进行条件判断
  • 如果不满足条件,就显示一个错误,但不会中断程序执行。这样就相当于提示用户,内部状态不正确
  1. console.assert(false, '判断条件不成立')
  2. // Assertion failed: 判断条件不成立
  3. // 相当于
  4. try {
  5. if (!false) {
  6. throw new Error('判断条件不成立');
  7. }
  8. } catch(e) {
  9. console.error(e);
  10. }

console.time(),console.timeEnd()

  • 这两个方法用于计时,可以算出一个操作所花费的准确时间
  1. // time方法表示计时开始,timeEnd方法表示计时结束
  2. // 它们的参数是计时器的名称
  3. // 调用timeEnd方法之后,控制台会显示“计时器名称: 所耗费的时间”
  4. console.time('Array initialize');
  5. var array= new Array(1000000);
  6. for (var i = array.length - 1; i >= 0; i--) {
  7. array[i] = new Object();
  8. };
  9. console.timeEnd('Array initialize');
  10. // Array initialize: 1914.481ms

console.group(),console.groupEnd(),console.groupCollapsed()

  • console.group和console.groupEnd这两个方法用于将显示的信息分组
  • 它只在输出大量信息时有用,分在一组的信息,可以用鼠标折叠/展开
  1. console.group('一级分组');
  2. console.log('一级分组的内容');
  3. console.group('二级分组');
  4. console.log('二级分组的内容');
  5. console.groupEnd(); // 二级分组结束
  6. console.groupEnd(); // 一级分组结束
  7. // console.groupCollapsed方法与console.group方法很类似
  8. // 唯一的区别是该组的内容,在第一次显示时是收起的(collapsed),而不是展开的

console.trace(),console.clear()

  • console.trace方法显示当前执行的代码在堆栈中的调用路径
  • console.clear方法用于清除当前控制台的所有输出,将光标回置到第一行
  • 如果用户选中了控制台的“Preserve log”选项,console.clear方法将不起作用
  1. console.trace()
  2. // console.trace()
  3. // (anonymous function)
  4. // InjectedScript._evaluateOn
  5. // InjectedScript._evaluateAndWrap
  6. // InjectedScript.evaluate
  7. console.clear()

debugger 语句

  • debugger语句主要用于除错,作用是设置断点
  • 如果有正在运行的除错工具,程序运行到debugger语句时会自动停下
  • 如果没有除错工具,debugger语句不会产生任何结果,JavaScript 引擎自动跳过这一句
  1. for(var i = 0; i < 5; i++){
  2. console.log(i);
  3. if (i === 2) debugger;
  4. }
  5. // 上面代码打印出0,1,2以后,就会暂停,自动打开源码界面,等待进一步处理

三、章节链接

「@浪里淘沙的小法师」