关于 JavaScript

  1. JavaScript 原名 LiveScript,LiveScript 创建之初,Sun 公司推出的 Java 的股价正一飞冲天,为了搭上这个顺风车,LiveScript 改名为 JavaScript。

  2. JavaScript 是 Netscape 为了与 IE 对抗而创建的语言,同样,微软也为 IE 创建了 JScript,这种语言与 JavaScript 很相似。浏览器大战就此爆发。

    后来,Netscape 提交了对 JavaScript 进行标准化的申请,ECMAJavaScript 就此诞生。ECMAJavaScript 是所有 JavaScript 实现(无论是浏览器还是其他环境中)的标准语言定义。但 JavaScript 仍被看做业余语言。直到 Google 发布了 Google Maps,向全世界展示了 JavaScript 的强大威力。在 Google 的推动下,JavaScript 终于受到了专业开发人员的尊敬。随着原来越多的人关注 JavaScript,很多杰出的程序员致力于改善 JavaScript 解释器,使得其运行性能有了极大的提高。

  3. 过去,JavaScript 只能运行在浏览器中,浏览器就是 JavaScript 的运行环境,而且 JavaScript 必须要嵌入 HTML 中,才能被浏览器执行。

    而现今,JavaScript 已经发展为一种通用的脚本语言,其不再只能用于前端开发,还能用于从图形工具到音乐应用程序的众多应用程序中,还能用于服务器端编程。

  4. 现在的 JavaScript 使用了高级编译技术(生成中间字节码),其动力强劲,代码执行速度几乎能与编译型语言媲美。

  5. JavaScript 对代码的语法、结构要求不严格,这样,代码编写更容易,但代码的维护和修改却很难,所以现在产生了很多 JavaScript 语法规范。编写代码时,最好遵守这些规范。

  6. JavaScript 也是一种 C-like 语言,很多语法与 C/C++ 相同,同时也具有一些脚本语言所特有的特性。

变量与常量

变量与常量的命名

  1. JavaScript 中的变量名可以使用 $(开头或包含在变量名中),还可以使用中文,其他与 C/C++ 相同

  2. _$ 打头的变量名通常用于 JavaScript 库,所以尽量避免起这样的变量名。

  3. 约定的命名规则:

    • 变量、函数名、类的公有成员使用小驼峰式命名法除了第一个单词外的其他单词的首字母大写)。

    • 类的私有成员在小驼峰式命名法的基础上在最前面加下划线

    • 常量全大写、单词间用下划线隔开。

    • 构造函数、类名使用大驼峰式命名法(将构造函数名中的每个单词的首字母都大写)。

变量与常量的声明与实例化

1
2
3
4
let x;       // 仅仅是声明了一个变量 x,此时并没有为 x 分配空间
let x = 10; // 声明并实例化变量 x,此时 x 被分配了空间

const Y = 10; // 声明并实例化一个常量。常量必须在声明的同时就实例化。

关键词 let 与 var

事实上,最初在 JavaScript 中声明变量时使用的是 varlet 关键词是在 ES6 中新引入的,它与 var 主要有以下几个区别:

  1. let 声明的变量只在它所处的代码块中有效。如在 for 循环中使用 letvar

    1
    2
    3
    4
    5
    6
    7
    for (let i = 0; i < 10; i++)
    console.log(i);
    console.log(i); // undefined

    for (var i = 0; i < 10; i++)
    console.log(i);
    console.log(i); // 10
  2. 重复声明用 let 声明的变量时会报错,而重复声明用 var 声明的变量时,其值不变:

    1
    2
    3
    4
    5
    let x = 1;
    let x; // Uncaught SyntaxError: Identifier 'x' has already been declared

    var y = 1;
    var y; // 不会报错,y 的值仍然为 1

显而易见,let 要比 var 更符合逻辑,所以尽可能使用 let 而不是 varvar 关键词会被逐渐淘汰。

数据类型

JavaScript 中的数据类型:String, Number, Boolean, null, undefined, Object

JavaScript 中的值除了基本数据类型,其他都是对象

number.toFixed(3):将 number 保留 3 位小数,并四舍五入。

特殊类型和值

  1. Infinity-Infinity

    • Infinity 的类型是 number

    • 任何超过浮点数上(下)限的数,其值为 Infinity-Infinity)。浮点数上限:1.7976931348623157E+10308

    • 0/0NaN10/0Infinity

    • 两个 Infinity 相加、相乘的结果是 Infinity;两个 Infinity 相减、相除的结果是 NaN

  2. NaN(Not a Number):

    • 虽然 NaN 的全称是 “不是一个数字”,但应该认为它是 “无法表示的数字”,因为 typeof NaN --> number

    • NaN 用来表示计算机无法表示的数值结果。如:0/0, "food"*100, Math.sqrt(-9)

    • NaN任何值都不相等,包括它本身。即 NaN != NaN。因为 NaN 指的是 “无法表示的数字”,但并非所有无法表示的数字都相等,如:sqrt(-1)sqrt(-2),所以 NaN != NaN

    • 判断一个值是否为 NaN,不能 if (aNum == NaN) {...},而是要 if (isNaN(aNum)) {...}。使用函数 isNaN() 来判断。因为 NaN 不与任何值相等,也就无法判断通过第一种方式判断。

    • 函数 isNaN():当参数在被转换为数字后是 NaN 时,返回 true(如:isNaN("123") --> falseisNaN(true) --> falseisNaN("abcd") --> true)

  3. undefined

    • undefined 是一个值,它的类型是 undefined

    • 仅仅声明而没赋值的变量的值是 undefined

    • 没有 return 语句的函数返回的值是 undefined

    • 稀疏数组中不存在的元素的值是 undefined

    • 不存在的属性的值是 undefined

    • 创建对象时不能赋给属性的值是 undefined

  4. null

    • 在原来的规范中,null 的类型是 object,在最新的规范中,null 的类型是 null

    • 在应该提供一个对象,但无法创建或找到时,将提供 null

JavaScript 中的假值

undefined, null, NaN, 0, ''(空字符串)

其他都是真值

字符串

模板字符串

模板字符串使用反引号创建,使用模板字符串可以方便地定义多行字符串,还可以对字符串进行格式化。

  1. 创建多行字符串:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 不使用模板字符串:
    // 方法一:
    let str = 'string text line 1\n' +
    'string text line 2';
    // 方法二:
    let str = 'string text line 1\n\
    string text line2';

    // 使用模板字符串:
    let str = `string text line 1
    string text line 2`;
  2. 格式化字符串:

    1
    2
    3
    4
    5
    6
    let a = 1;
    let b = 2;
    console.log(`a + b = ${a + b}`); // a + b = 3

    let name = 'marsvet'
    console.log(`Hello, ${name}!`); // Hello, marsvet!

字符串常用属性和方法

  • string.length 字符串长度

  • string.charAt(i) 返回 string 中第 i 个字符(完全可以用 string[i] 代替)

  • string.split(分隔符) 分割字符串,将结果作为数组返回(与 Python 完全相同)

  • string.trim() 删除首尾空白(类似于 Python 的 strip()

  • string.toLowerCase() 返回全小写字符串

  • string.toUpperCase() 返回全大写字符串

  • string.replace(oldString|RegExp, newString) 替换指定字符串。参数为两个字符串,第一个参数还可以是正则表达式。注:该方法只能替换满足条件的第一个字符串,如果想替换所有满足条件的字符串,需要使用正则表达式(主要是用它的 g 参数)。如:

    1
    str.replace(/and/g, "AND");    // 将所有 "and" 替换为 "AND"
  • string.indexOf(Pattern[, start]) 在 string 中查找 Pattern,返回下标,如果未找到,返回 -1(只返回第一个目标的开始下标)。第二个参数为开始查找的位置,可省略,默认为 0。(该方法相当于 Python 中的 find()

  • string.lastIndexOf()indexOf() 类似,但查找最后一个匹配的结果

  • string.match(RegExp对象) 正则匹配

  • string.slice(start[, end]) 字符串切片。功能与 Python 中的字符串切片完全相同(支持负数索引

  • string.substring(indexStart[, indexEnd]) 返回 stringindexStart ~ indexEnd 之间的字符串。(不支持负数索引

    • 如果省略 indexEndsubstring 提取字符一直到字符串末尾
    • 如果任一参数小于 0 或为 NaN,则被当作 0;如果任一参数大于 stringName.length,则被当作 stringName.length
    • 如果 indexStart 大于 indexEnd,则 substring 的执行效果就像两个参数调换了一样,也就是说 substring()会取 indexStartindexEnd 中较小的值作为 start ,较大的值作为 end
  • string.substr(start[, length]) 返回字符串中从指定位置开始到指定字符数的字符。(start 参数支持负数

    • 如果 start 为正值,且大于或等于字符串的长度,则 substr 返回一个空字符串
    • 如果 start 为负值,则被看作 strLength + start,其中 strLength 为字符串的长度(与 Python 的负数索引原理相同)。
    • 如果 start 为负值且 start 的绝对值大于字符串的长度,则 substr 使用 0 作为开始提取的索引。
    • 如果 length0 或负值,则 substr 返回一个空字符串。如果忽略 length,则 substr 提取字符,直到字符串末尾
  • string.charCodeAt(index):返回字符串下标为 index 处的字符的 UTF-16 码值。

  • String.fromCharCode(num1, num2, ...):参数为 UTF-16 码值。该方法将每个参数转化为对应的字符,并将它们连接起来形成一个字符串返回。注意该方法是 String 的静态方法,所以应该像这样使用:String.fromCharCode(num1, num2, ...),而不是作为开发者自己创建的 string 对象的方法使用。

运算符与类型转换

  1. ?: 运算符与 C/C++ 相同

  2. JavaScript 也有 ++--+=-=*=/=%= 等运算符

  3. ++= 可以用来连接字符串(与 Python 相同)

  4. JavaScript 中 / 运算符是精确计算,不会取整

  5. typeof 变量名 返回变量类型,详细内容

  6. 变量名 instanceof 类型 判断该变量是否该类型,详细内容

    • JavaScirpt 中有 in 运算符,但用法与 Python 不同。TODO

JavaScript 中的比较运算符及进行比较运算时的隐式类型转换

  1. == 运算符与 C/C++ 不同,JavaScript 中的 == 运算符在对比两个值时,如果两个值不是同类型,会先临时转换为同类型再比较。

    转换规则

    • 字符串转换为数字。如:

      • "123" --> 123
      • "abcd" --> NaN
      • "" --> 0
    • true --> 1, false --> 0

    总之,当两个值的类型不同时,JavaScript 会将它们转换为数字再比较

    另外,undefined == null

  2. 同样,<, >, <=, >=, != 等运算符在比较不同类型的值时,也会先进行类型转换,转换规则与 == 相同

  3. ===(等同运算符):当两个值的类型和值都相同时,结果为 true,否则都为 false

  4. 没有 <==, >==,但有 !==

  5. 在 JavaScript 中,=== 用的更多,因为它更安全。但如果掌握了 == 运算符的使用规则,在某些情况下使用 == 更方便。比如在比较数字和字符串时。

  6. 对比两个对象是否相等:只是对比对象的引用是否相同,即两个引用是否指向同一个对象。(与 Python 相同)

    此时 ===== 均可,因为对象的类型都是 object

  7. 总结:JavaScript 中,同类型数据的比较与 C-like 相同(如 字符串的比较),但不同类型比较时会先转换成同类型(除 ===

进行 +, -, *, / 运算时的隐式类型转换

若有两个操作数,一个是数字,一个是字符串,则

  • 如果操作符是 +,数字被转换为字符串,并拼接。(注意:因为 + 是从左向右运算,所以 1+2+"abcd" --> "3abcd",而 "abcd"+1+2 --> "abcd12"

  • 如果操作符是 -, *, /,字符串被转换为数字,并进行数学运算。(转换规则同 == 运算符)

其他发生隐式类型转换的情况

  1. true 取负:-true --> -1

  2. 布尔值与字符串相加:true + "string" --> "truestring"

显式类型转换

使用函数 Number(), String(), Boolean(), Object()

流程控制

  1. if-elseswitchwhiledo-while 语句的用法与 C/C++ 完全相同。

  2. breakcontinue 的基本用法与 C/C++ 相同。

    JavaScript 中 break 还能用于跳出自定义的代码块(不局限于 循环 和 switch):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let cars = ["BMW","Volvo","Saab","Ford"];
    list: /* list 是标记名,下面大括号内的内容都属于 list */
    {
    console.log(cars[0]);
    console.log(cars[1]);
    console.log(cars[2]);
    break list; /* 跳出 list 代码块 */
    console.log(cars[3]);
    console.log(cars[4]);
    console.log(cars[5]);
    }
  3. for 循环有三种写法

    1. for (let i = 0; i < n; i++) { }。这种写法与 C/C++ 相同。
    2. for (let i of array) { }。这种写法与 Python 相同,是最常用的写法,用来遍历数组。
    3. for (let i in object) { }。注意不要忘了 let 关键词。这种写法用来遍历对象的属性。

数组

JavaScript 中数组的大多数操作与 C/C++ 相同,下面是不同点:

  1. 可以存储不同类型的值。

  2. 数组长度可变。

  3. 数组也是一种对象,拥有属性和方法。

  4. 可以 array[n] = x 改变该索引上的值,如果该索引没有元素,则添加元素。注意:这样添加新元素时,必须小心指定索引,否则数组可能是稀疏的,即存在 “空洞”(如索引 0 和 2 处有值,而 1 处没有值)。数组稀疏未必是坏事,但使用时必须特别小心。稀疏数组中没有赋值的地方值是 undefined

数组的定义:let array = [...]let array = new Array()

数组常用属性和方法

  • 数组长度:array.length,注意 length 是属性,不是方法

  • array.push(元素) 在尾部添加元素。

  • array.pop() 删除尾部元素并将其返回。

  • array.shift() 删除头部元素并将其返回。

  • array.unshift(元素) 在头部添加元素。

  • 数组切片:array.slice() (该方法与 Python 数组切片原理相同。支持负数索引

  • array.splice(start[, deleteCount, item1, item2, ...]):从 start 开始删除 deleteCount 个元素,并将 item1, item2, ... 添加到 start 的位置。(此方法会改变原数组,并将删除的元素作为一个数组返回

    • start 可以是负数。

    • start 如果超出了数组的长度,则从数组末尾开始添加内容。

    • 如果 deleteCount 被省略,或大于 start 之后的元素的总数,则从 start 开始及后面的元素都将被删除。

    • 如果 deleteCount 是 0 或者负数,则不移除元素。

    • 该方法可以用来向数组中插入元素、删除或修改元素。如:

      1
      2
      3
      array.splice(1, 0, "item");	// 在第一个元素后面插入一个元素
      array.splice(1, 1); // 删除第一个元素
      array.splice(0, 1, "item"); // 删除第 0 个元素,并在原位置插入一个元素 "item"。(相当于将第 0 个元素修改为 "item")
  • array.forEach(function(item) {...}):遍历数组元素,为每个元素执行 function

  • array.filter(function(val){...}):数组元素过滤器。参数是一个函数,该方法将数组的元素逐个传给该函数,函数返回 true 的项会保留在数组中,返回 false 的项会被过滤出数组。(该方法不会改变原数组

    1
    2
    3
    4
    5
    6
    let oldArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let newArray = oldArray.filter(function(val) { if (val < 6) return true; });
    // 传入的函数的参数是 val(数组中元素的值)

    console.log(newArray); // 结果:[1, 2, 3, 4, 5]
  • array.map(function(val){...}):参数是一个函数,该方法将数组的元素逐个传给该函数,函数按照指定规则处理元素并返回处理后的结果,最后 map 将所有结果打包成数组返回(该方法不会改变原数组

    1
    2
    3
    4
    let oldArray = [1, 2, 3, 4, 5];
    let newArray = oldArray.map(function(val) { return val * 3; });

    console.log(newArray); // 结果:[3, 6, 9, 12, 15]
  • array.reduce(function(preValue, value), preValue的初始值):该方法通常用于对一个数组进行汇总,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let array = [10, 20, 30, 40, 50, 60];

    // 传入的函数有两个参数,第一个参数是上一次迭代时返回的值,第二个参数是本次迭代的值
    // 每次迭代返回的值会作为下一次迭代时 preValue 的值,最后一次迭代返回的值就是 reduce 方法的返回值
    let total = array.reduce(function(preValue, value) {
    return preValue + value;
    }, 0) // reduce 方法的第二个参数回作为 preValue 的初始值

    console.log(total); // 结果:210
  • 数组拼接:array.concat(otherArray)。将 otherArray 拼接在 array 后面并返回(arrayotherArray 都不会改变

  • 数组排序:array.sort([compare])原地排序

    如果没有传入比较函数,它将把值全部转成字符串,并按照字母顺序进行升序排序。

    如果想按照其他标准进行排序,或数组的值的类型是自定义的对象,就需要提供比较函数 compare 来自定义排序规则。该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

    • 若 a 大于 b,在排序后的数组中,想让 a 出现在 b 之后,则返回一个大于 0 的值。
    • 若 a 等于 b,则返回 0。
    • 若 a 小于 b,则返回一个小于 0 的值。

    compare 函数的定义(实例):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 升序排序数组
    function compare(num1, num2) {
    if (num1 > num2) {
    return 1;
    } else if (num1 === num2) {
    return 0;
    } else {
    return -1;
    }
    }

    如果数组中的值可以相减,则可以简化比较函数:

    1
    2
    3
    function compare(num1, num2) {
    return num1 - num2;
    }
  • 数组逆序:array.reverse()原地逆序

  • array.join(连接符):使用连接符将数组元素连接起来形成一个字符串并返回该字符串。

    1
    2
    3
    4
    let array = ["Join","me","into","a","string"];
    let string = array.join(" "); // 空格作为连接符

    console.log(string) // 结果:"Join me into a string"

    该方法与字符串的 string.split(分隔符) 相对应。

作用域、函数和闭包

函数的定义

  1. 常规方法定义函数(函数声明

    1
    2
    3
    function func_name() {
    return;
    }
  2. lambda 表达式定义函数

    1
    2
    3
    4
    let func_name = function(){
    return;
    }
    // 赋值号后面是 lambda 表达式,它返回一个指向函数的引用,该引用被赋值给一个变量,这样就创建了一个函数。
  3. 两种方式定义函数的异同:

    1. 两种方式创建的函数调用方法相同。

    2. 变量都只是存储函数的引用,实际的函数代码及其环境(闭包)存储在其他地方。

    3. 使用函数声明时,解释器先给函数及其环境分配空间,然后创建一个与函数名同名的变量,同时将函数的引用赋值给该变量(这个赋值是隐式的);使用 lambda 表达式定义函数时,解释器先给函数及其环境分配空间,然后返回函数的引用,开发者可以直接使用该引用,或将该引用赋值给一个变量以调用该函数(这个赋值是显式的)

    4. 解释器在执行 JavaScript 代码时,先处理所有函数声明,再从头到尾执行其他代码(包括 lambda 表达式)。

    5. 函数声明是一个完整的语句,而 lambda 表达式只是一个表达式

    总之,使用函数声明时,函数在执行代码前创建;而使用 lambda 表达式创建函数时,函数在运行阶段执行代码时创建。所以,尽可能使用函数声明

  4. JavaScript 函数的形参可以省略。

  5. 函数声明可以放在任何地方,且可在任何地方调用它们。在代码的任何地方,用函数声明创建的函数都是已定义的,这被称为提升

  6. 函数嵌套定义时,在函数内部的什么地方可调用嵌套函数的规则,与在整个代码的什么地方可调用全局函数的规则相同。也就是说,在函数内部,如果使用函数声明创建了一个嵌套函数,那么在这个函数的函数体的任何地方,嵌套函数都是已定义的;如果使用 lambda 表达式创建了一个嵌套函数,则在这个函数的函数体内,仅当 lambda 表达式执行后,嵌套函数才是已定义的。

变量的作用域

  1. 如果已经声明了一个全局变量,在局部作用域声明一个同名的局部变量必须使用 let 关键词,否则会视为给全局变量赋值

    1
    2
    3
    4
    5
    6
    7
    let a = 1
    let b = 2
    function func() {
    let a = 3
    b = 4
    }
    // 函数体内用 let 声明的变量 a 是局部变量,修改它的值不影响全局变量 a。b = 4 修改了全局变量 b
  2. 如果在函数体内使用未声明的变量,它会被视为全局变量,即使它是在函数体内被首次使用的。

    1
    2
    3
    function func() {
    a = 1 // a 是全局变量。
    }

    上面的代码就相当于

    1
    2
    3
    4
    let a
    function func() {
    a = 1
    }

函数是一等值

在编程语言中,所有基本数据类型的值都是一等值。它们可以被赋值给变量或存储在数组、对象中,可以作为函数的参数和返回值。

在 JavaScript 中,函数也是一等值。相对于其他一等值,函数的不同之处在于我们可以调用它

匿名函数

使用lambda 表达式定义的函数,可以不给它起名,这样,它就是个匿名函数

灵活使用匿名函数可以在很多情况下简化代码的书写,增加代码可读性。

闭包

先明确几个概念:

  1. 局部变量:在函数体内定义的变量(包括所有的形参)。

  2. 自由变量:并非在函数体内定义,但在函数体内使用了的变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let beingFunny = true;
    let notSoMuch = false;
    function justSayin(phrase) { // phrase 是形参,所以是局部变量
    let ending = ""; // ending 在函数体内定义,所以是局部变量
    if (beingFunny) { // beingFunny 在函数体外定义,在函数体内被使用,所以是自由变量
    ending = " -- I'm just sayin!";
    } else if (notSoMuch) { // notSoMuch 在函数体外定义,在函数体内被使用,所以是自由变量
    ending = " -- Not so much.";
    }
    alert(phrase + ending);
    }
  3. 敲定:给函数运行所需要的每个自由变量都提供了值后,该函数才能运行,此时就说这个函数被敲定了。(对于上例,如果变量 beingFunnynotSoMuch 没有值,则函数 justSaying 没有被敲定,所以该函数无法运行)

  4. 环境:每个函数都有一个环境,环境中存储的是函数运行所需要的自由变量及其值

  5. 闭包:函数 + 环境 = 闭包

函数运行所需要的自由变量的值会从其环境中取得。

函数被作为参数传递,或作为返回值返回时,其环境会被同时传递或返回。也就是说,被作为参数传递和作为返回值返回的是闭包,而不仅仅是函数

下面是几个应用闭包的例子:

  1. 将闭包作为返回值以避免全局变量导致的命名冲突(实现一个简单的计数器)

    不使用闭包:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let count = 0;             // 定义一个全局变量

    function counter() { // 每次调用时 count 加 1,并返回结果
    count = count + 1;
    return count;
    }

    console.log(counter());
    console.log(counter());
    console.log(counter());

    使用闭包:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function makeCounter() {     // 定义函数 makeCounter
    let count = 0; // 定义一个局部变量 count

    function counter() { // 嵌套定义函数 counter
    count++; // 对函数 counter 来说,count 是它的自由变量,存储在其环境中
    return count;
    }

    return counter; // 将函数 counter 及其环境一起返回
    }

    let doCount = makeCounter(); // 调用函数 makeCounter,它创建函数 counter,并将 counter 与其环境一起返回。换句话说,它创建了一个闭包。从 makeCounter 返回的闭包的引用存储在 doCount 中。
    console.log(doCount()); // 每次调用函数 doCount(也就是 counter) 时,当遇到变量 count,JavaScript 解析器在 counter 的环境中获取 count 的值,将 count 加 1 并返回。
    console.log(doCount());
    console.log(doCount());
    • 对于本例,如果按照 C/C++ 的理解方式理解,函数 makeCounter 在被调用完毕后,变量 count 会被删除,因为 count 是定义在 makeCounter 中的局部变量,这导致调用 doCount 会发生错误。但 JavaScript 并不是这样,因为函数会和它的环境一起返回函数运行所需要的自由变量的值会从其环境中取得。,所以 doCount 可以正常调用。

    • 在本例中,不使用闭包和使用闭包,代码的功能完全相同,但不使用闭包的写法有一个很大的问题:全局变量 count 很容易导致命名冲突。而使用了闭包以后,count 并不是全局变量,因此避免了命名冲突。

    • 另外,使用了闭包后,除了调用 doCount 外,没有其他任何办法能改变和使用 count 的值,这就将 count 给保护了起来。

函数的 arguments 对象

在每个函数中,都有一个名为 arguments 的对象可供使用。形参列表中没有这个对象,但每当函数被调用时,都可以通过变量 arguments 来使用它。

对象 arguments 包含传递给函数的所有实参,可以像使用数组一样使用它,只不过它没有数组的一些方法(如 pop)。通过使用 arguments,可以创建这样的函数:接收数量可变的实参,并根据传入的实参数量执行不同的操作。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function printArgs() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}

printArgs("one", 2, 1+2, "four");
/*
结果:
one
2
3
four
*/

另外,可以同时使用形参和 arguments,以编写形参数量可变的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createArray(num) {
if (arguments.length > 1) {
let array = [];
for (let i = 0; i < arguments.length; i++) {
array.push(arguments[i]);
}
return array;
} else if (arguments.length === 1 && num instanceof number) {
let array = new Array(num);
return array;
} else {
return;
}
}
/*
该函数的作用是:
当传入的参数个数大于 1,则新建并返回一个数组,这些参数作为数组的元素。
当传入的参数个数为 1 并且 num 是数字时,新建并返回一个数组,num 为数组的元素个数。
*/

这个例子很鸡肋且不合逻辑,它仅仅是一个例子。

arguments 的几个属性:

  • arguments.callee指向当前执行的函数。

  • arguments.length:传递给当前函数的参数数量。

箭头函数

基础语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(参数列表) => { 函数体 }

// 没有参数的函数头应该写成一对圆括号。
() => { 函数体 }

// 当只有一个参数时,可以省略圆括号
x => { 函数体 }

// 当函数体中只有一个表达式,且该表达式包含 return 时,可以省略大括号和 return
(x, y) => { return x + y; };
(x, y) => x + y;

// 箭头函数支持默认参数
(x=1, y=2) => x + y;

箭头函数的作用:

  1. 更简洁的函数定义。

  2. 不创建自己的 this

    在箭头函数出现之前,其他函数都有它自己的 this 值,比如普通函数的 this 指向它自己,对象构造函数的 this 指向新产生的对象。

    箭头函数不会创建自己的 this,而是从自己的作用域链的上一层继承 this,如:

    1
    2
    3
    4
    5
    6
    7
    8
    function Person() {
    this.age = 0;
    this.printAge = () => {
    console.log(this.age); // this 并不指向该箭头函数,而是继承上一层函数的 this(即 Person 的 this),Person 的 this 指向其产生的对象,所以,这里的 this 指向 p 对象。
    }
    }

    let p = new Person();
  3. 不创建自己的 arguments 对象:

    与不创建 this 的原理类似,箭头函数不会创建自己的 arguments 对象,而是使用其环境中的 arguments 对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let arguments = [1,2,3];
    let arr = function() {
    for (let i of arguments)
    console.log(i);
    }
    arr(); // 没有输出

    let arguments = [1,2,3];
    let arr = () => {
    for (let i of arguments)
    console.log(i);
    }
    arr(); // output: 1 2 3

对象

在 JavaScript 中,万物皆对象(包括函数)。这与 Python 的理念相同。

使用 对象字面量 创建对象

1
2
3
4
5
6
7
8
let car = {
cost : 3.14,
"on sale" : true, // 属性名包含空格时,需要加引号;不包含时可加可不加。
2019: "2019", // 属性名可以以数字开头。
drive: function() { // function 后不用再加函数名,属性名就是该函数的名字
console.log("ZOOM, ZOOM")
}
}
  • 也可创建空对象:

    1
    2
    3
    let object = {};
    let object = new Object();
    // 两种写法均可
  • 方法实际上也是属性,只不过属性的值是一个匿名函数。

  • 在 ES6 中,引入了对象字面量的增强写法,可以更方便地定义对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let name = "john";
    let age = 18;
    let weight = 60;

    // 如果要使用前面已经定义过的变量,可以直接写变量名,省略冒号和变量的值
    let person = {
    name,
    age,
    weight
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 可以使用以下写法定义方法,简化书写
    let dog = {
    run() {
    console.log("I am running.");
    },
    eat() {
    console.log("I am eating.");
    }
    }
  • 使用对象字面量不能批量创建对象,要想批量创建,需要用到对象构造函数

属性和方法的调用、修改、添加和删除

  1. 调用:

    • 对象外:通过 object.attrobject["attr"] 获取属性,object.func()object["func"]() 调用方法。

    • 对象中:与 Python 的 self 一样,JavaScript 中也有 this。在对象的方法中可以使用 this.attrthis["attr"] 来获取属性,使用 this.func()this["func"]() 来调用方法。

    • 注意:如果属性名或方法名以数字开头包含空格,只能用中括号的方式调用且加引号。否则两种调用方法均可,且使用中括号方式调用时不用加引号

  2. 修改或添加: object.newattr = value

  3. 删除:delete object.attr,删除成功会返回 true,失败返回 false。如果要删除的属性不存在,也返回 true

  4. 不存在的属性的值是 undefined

  5. delete 删除受保护的对象的属性会失败,返回 false

  6. 可以使用 for (let i in object) { } 遍历对象,但此时不能使用 . 运算符调用对象属性值(如:object.i),只能用中括号调用(object[i]

JavaScript 中对象的存储

1
2
let x = 1        // 将 1 存放在 x 中
let ob = {...} // 将对象的引用放在 ob 中,而对象被放在另一个地方,这个引用指向对象。(想象 ob 只能存放一个基本数据类型的值,对象太大了存不下,所以存放对象的引用)
JavaScript 中对象的存储

正因如此,将一个对象赋值给另一个变量时,只是将引用赋值了过去(引用传递

对象构造函数

使用对象构造函数可以批量创建同类型对象。

对象构造函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Dog(name, sex, weight) {       // 对象构造函数名使用 大驼峰式命名法
this.name = name; // 属性名和形参名相同,这是约定
this.sex = sex;
this.weight = weight;
this.bark = function() { // 显然,也可以定义方法
return "wang wang!";
};
this.getString = function() {
console.log(this.name + " says: " + this.bark());
};

let privateProp = "I am a private property";
// 使用 this.prop 的方式定义的属性和方法是公有的,可以直接在对象外调用;使用 let 关键词定义的属性和方法是私有的,在对象外不可访问。

this.getPrivateProp = function() { // 定义获取私有属性 privateProp 的公有方法
return privateProp;
// 公有属性的调用方式必须为 this.publicProp;调用私有属性可以直接写属性名(方法同)。
};
}

使用对象构造函数创建对象

1
let fido = new Dog("Fido", "female", "37");

工作原理(主要是理解 new 运算符的工作原理):

  1. new 运算符首先创建一个空对象

  2. 接下来,new 将空对象的引用存储在 this 中,这样,this 就指向了这个空对象。

  3. 调用函数 Dog,并将 "Fido", "female", "37" 作为参数传给它。

  4. 执行这个函数的代码。由于这时 this 指向刚刚创建的空对象,所以执行 this.name = "Fido" 就等于为这个空对象添加了一个新属性 name,并将 "Fido" 赋值给它。其他属性同理。

  5. Dog 函数执行完毕后,new 运算符自动this(也就是指向新对象的引用) 返回。我们将其赋值给变量 fido,之后就可以通过 fido 使用该对象了。

千万注意不要忘记写 new

要灵活使用对象字面量和对象构造函数

使用对象字面量定义对象的代码更整洁可读性更好,所以如果不需要批量创建对象时,推荐使用对象字面量。当然,如果需要批量创建,就需要用到对象构造函数了。

另外还有对象字面量和对象构造函数配合使用的情况:

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
29
30
31
32
33
34
35
36
let cadiParams = {                     // 定义 cadiParams 对象
make: "GM",
model: "Cadillac",
year: 1955,
color: "tan",
passengers: 5,
convertible: false,
mileage: 12892
};

let cadi = new Car(cadiParams); // 将 cadiParams 对象作为参数传参

function Car(params) { // 参数只有一个
this.make = params.make;
this.model = params.model;
this.year = params.year;
this.color = params.color;
this.passengers = params.passengers;
this.convertible = params.convertible;
this.mileage = params.mileage;
this.started = false;

this.start = function() {
this.started = true;
};
this.stop = function() {
this.started = false;
};
this.drive = function() {
if (this.started) {
console.log("Zoom, Zoom!");
} else {
console.log("You need to start the engine first.");
}
};
}

这样写可以避免对象构造函数参数过多导致的难以阅读和维护

详谈 typeof 和 instanceof 运算符

typeof 变量或常量 可以获取数据类型,如 typeof 123 –> numbertypeof "123" –> string。但对于任何对象,它只会返回 object,而无法具体判断该对象是 小狗对象 还是 汽车对象,typeof 任何对象 –> object

变量或常量 instanceof 数据类型 可以判断该变量或常量是否该数据类型,是返回 true,否返回 falseinstanceof 运算符的功能比 typeof 更强大,它可以判断具体的对象类型,如:fido instanceof Dog –> truefido 是由构造函数 Dog 生成的一个对象,所以 instanceof 运算符认为 fidoDog 类型。

JavaScript 内置的对象构造函数

1
2
3
let now = new Date();       // 新建一个表示当前日期和时间的日期对象
let a = new Array(); // 新建一个空的数组。等价于 let a = [];
let ob = new Object(); // 新建一个空对象。等价于 let ob = {};

JavaScript 有很多内置的对象构造函数,使用这些函数可以轻松构建出多种多样有用的对象,要想全面了解这些对象构造函数以及相应的对象,可以参考 《JavaScript 权威指南》。

  • TODO

JavaScript 原型与继承

什么是原型?为何以及如何使用原型?

每使用对象构造函数实例化一个对象,都会创建一组新的属性和方法,也就是说,方法无法被重用。这会导致大量重复的方法占用计算机资源,严重影响应用程序的性能。

JavaScript 使用原型式继承解决这个问题。每个对象构造函数都有一个属性 prototype,将某个对象(原型)赋值给它,那么用该构造函数实例化的对象便可以使用原型的所有属性和方法。称该构造函数实例化的对象继承原型

注意:原型继承与 C++/Java 中类的继承完全是两码事

那么该如何使用原型呢?

将需要重用的属性和方法(主要是方法)从对象构造函数里删除,把它们放到一个对象里,并将该对象的引用赋值给对象构造函数的 prototype 属性。这样,用对象构造函数实例化的所有对象调用的方法都是原型里的那个方法,于是就实现了重用。

举个栗子:

  • 不使用原型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Dog(name, sex, weight) {
    this.name = name;
    this.sex = sex;
    this.weight = weight;
    this.bark = function() {
    console.log("wang wang!");
    };
    this.run = function() {
    console.log("run");
    };
    }

    每个小狗都有自己的 名字(name), 性别(sex), 体重(weight),所以这些属性不需要重用。但所有小狗的 barkrun 方法都是一样的,为了节省计算机资源,就需要重用这些方法。下面使用原型来重用 barkrun

  • 使用原型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 1.将对象构造函数中的 bark 和 run 方法删除
    function Dog(name, sex, weight) {
    this.name = name;
    this.sex = sex;
    this.weight = weight;
    }

    // 2.将 bark 和 run 方法放到一个对象里,并将该对象的引用赋值给对象构造函数的 prototype 属性
    Dog.prototype = {
    bark: function() {
    console.log("wang wang!");
    },
    run: function() {
    console.log("run");
    }
    };

    // 3.创建一个小狗对象 fido,这个小狗对象只包含 name, sex, weight,并不包含 bark 和 run
    let fido = new Dog("Fido", "female", 37);

    // 4.调用 fido 的 bark 方法,JavaScript 解析器在 fido 对象中查找 bark,没有找到,于是去查找 Dog.prototype 指向的对象,也就是原型,这时 bark 被找到了,于是 bark 被成功调用。
    fido.bark();
    JavaScript 原型继承的工作原理

    小狗对象并不包含 bark 方法,要想调用这个方法,必须到小狗对象的原型中查找。由于所有小狗对象共用一个原型,也就共用了一个 bark 方法。

    Dog.prototype 指向 Dog 的原型,所以如果需要向原型中添加属性和方法,可以

    1
    2
    3
    4
    5
    6
    7
    Dog.prototype.sit = function() {
    console.log(this.name + " is now sitting");
    };

    fido.sit();
    /* 注意:这里调用 sit 时,虽然 sit 处在原型中,但 this 仍指向 fido 对象。所以 this.name 的值是 "Fido"。
    sit 是通过 Dog.prototype.sit() 调用的,而不是让 this 指向 Dog 的原型,再通过 this.sit() 调用的。 */

重写原型

继承原型并不意味着必须与它完全相同。在任何情况下,都可以重写原型的属性和方法,为此只需在对象实例中提供它们即可。

举个栗子:

1
2
3
4
5
fido.bark = function() {
console.log("Woof woof!");
};

fido.bark(); // 结果为 "Woof woof!"

fido 从原型中继承的 bark 方法可以在控制台打印 “wang wang”,但这里我们重写了它,所以结果为 “Woof woof!”。

这很好理解:在调用 fido.bark() 时,JavaScript 解析器先在 fido 对象中查找有没有 bark 方法,显然有,所以直接调用它,都不需要去 fido 的原型中查找了。

另外,我们经常在方法中修改原型的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Dog(name, sex, weight) {
this.name = name;
this.sex = sex;
this.weight = weight;
}

Dog.prototype = {
sitting: false,

sit: function() {
if (this.sitting) {
console.log(this.name + " is already sitting");
} else {
console.log(this.name + " is now sitting");
this.sitting = true; // 修改 sitting 属性
}
}
};

let fido = new Dog("Fido", "female", 37);

fido.sit();

调用 fido.sit() 后,实际上原型的 sitting 属性并没有被修改(想想也知道,如果原型被修改了,那不就相当于所有小狗的 sitting 属性都被修改了?),而只是在 fido 中添加了一个 sitting 属性并将其设为 true,也就是说,sitting 属性在 fido 中被重写了。

建立原型链

对象可以继承多个原型,即原型链。这样不但可以重用原型的属性和方法,还可以重用原型的原型的属性和方法(整个原型链的属性和方法)。

举个例子:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 1.定义构造函数 Dog
function Dog(name, sex, weight) {
this.name = name;
this.sex = sex;
this.weight = weight;
}

// 2.指定 Dog 的原型
Dog.prototype = {
bark: function() {
console.log("wang wang!");
},
run: function() {
console.log("run");
}
};

// 3.定义构造函数 ShowDog
function ShowDog(name, sex, weight, handler){
this.name = name;
this.sex = sex;
this.weight = weight;
this.handler = handler;
}

// 4.新建一个 Dog 对象,将其作为 ShowDog 的原型,这样就构成了 "Dog原型 --> ShowDog原型(Dog对象) --> ShowDog对象" 的原型链
ShowDog.prototype = new Dog();
/* 这里没有给 Dog 传参。因为我们只是需要一个继承了 Dog 原型的 Dog 对象,不关心其具体的属性值。另外,即使我们传了参,ShowDog 实例也会总是重写这些属性。
没有传参的 Dog 对象的属性的值是 未定义(undefined) */

// 5.为 ShowDog 的原型添加新的方法。(因为 ShowDog 比 Dog 更多才多艺)
ShowDog.prototype.stack = function() {
console.log("Stack");
}
ShowDog.prototype.bait = function() {
console.log("Bait");
}
ShowDog.prototype.gait = function() {
console.log("Gait");
}

// 6.ShowDog 对象可以调用其原型的方法,还可以调用其原型的原型的方法。
let scotty = new ShowDog("Scotty", "male", 28, "Cookie");
scotty.stack();
scotty.bark();

定义构造函数 ShowDog 时也可以这样定义:

1
2
3
4
function ShowDog(name, sex, weight, handler) {
Dog.call(this, name, sex, weight); // 使用 Dog.call 可以复用函数 Dog 的代码
this.handler = handler;
}
Dog.call 详解

测试 Scotty 的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (scotty instanceof ShowDog) {
console.log("scotty is a ShowDog.");
}
if (scotty instanceof Dog) {
console.log("scotty is a Dog.");
}
if (scotty instanceof Object) {
console.log("scotty is a Object.");
}
/*
结果:
scotty is a ShowDog.
scotty is a Dog.
scotty is a Object.
*/

对象始祖:Object

Object任何原型链的终点,所有对象都是由 Object 派生而来的,包括 数组、函数、字符串等,因为在 JavaScript 中,一切皆对象

Object 实现了多个重要的方法,常用的有:

  1. 对象.hasOwnProperty(属性名) 判断该属性是否属于该对象。

  2. 对象.toString() 返回对象的字符串表示。常被重写以显示对象的简略介绍。

    1
    2
    3
    4
    5
    6
    7
    8
    Dog.prototype.toString = function() {
    return this.name + " is a Dog";
    }

    let fido = Dog("Fido", "female", 37);

    console.log(fido.toString());
    // 结果:Fido is a Dog.

扩展内置对象

通过给原型添加方法,可给其所有实例添加新功能。这不仅适用于自定义对象,还适用于内置对象

例如扩展字符串对象:

1
2
3
4
5
6
7
8
// 为字符串对象的原型添加方法 palinkdrome
String.prototype.palinkdrome = function() {
// some code.
}

// 此后定义的字符串就都可以调用 palinkdrome 方法了
let str = "abcdefg";
str.palinkdrome();

注意:给内置对象添加新方法时,名称一定不要与已经有的方法冲突!

另外,有些内置对象是不可扩展的,如 Array

函数式对象

d3.js 使用了一种编程风格,用这种风格创建的对象具有强大的灵活性和信息隐藏的能力,在 JavaScript 编程范型中,这叫做函数式对象。

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
29
30
31
32
33
34
35
36
37
function rect() {
let _rect = {}; // 创建对象

let _width = 100,
_height = 50,
_color = "black"; // 创建对象的属性并设置默认值,属性名约定以下划线开头。无法从外部直接访问属性。

_rect.width = function(w) { // 创建对象属性的外部访问接口
if (!arguments.length) return _width; // 如果没有参数,则该函数的作用是 getter,否则为 setter
_width = w;
return _rect; // 注意这里要将对象返回,以支持方法的级联调用
}

_rect.height = function(h) {
if (!arguments.length) return _height;
_height = h;
return _rect;
}

_rect.color = function(c) {
if (!arguments.length) return _color;
_color = c;
return _rect;
}

return _rect; // 对象创建完成后返回
}


let r1 = rect(); // 创建 rect 对象
console.log(r1.width()) // 获取 r1 的 width。output: 100

let r2 = rect()
.width(200)
.height(100)
.color("white"); // 创建对象并设置对象的属性,支持级联调用(就像 d3.js 一样)
console.log(r2.width()) // 获取 r2 的 width。output: 200

这个函数用到了闭包的概念。

注意与对象构造函数 的区别。

JavaScript 中的类

在 JavaScript 中,除了使用 对象构造函数函数式对象批量产生对象外,也可以使用

JavaScript 中类的概念是从 ES2015 才引入的,它实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。也就是说,class 关键词定义的类的使用方法和 对象构造函数 的使用方法相同。

类的定义

类的定义可以用两种方法:

  1. 类声明的方法定义:

    1
    2
    3
    4
    5
    6
    class Rectangle {
    constructor(height, width) {
    this.height = height;
    this.width = width;
    }
    }
  2. 类表达式的方法定义:

    1
    2
    3
    4
    5
    6
    let Rectangle = class {
    constructor(height, width) {
    this.height = height;
    this.width = width;
    }
    };

注意:无论使用以上哪种方式,类的定义都不会被提升,所以类的定义必须出现在实例化之前。

类体和方法定义

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
29
30
31
32
class Rectangle {
// 构造函数
constructor(height, width) {
this._height = height;
this._width = width;
}

// 静态方法
static xxx() {
// some code
}

// Getter
get area() {
return this.calcArea();
}

// Setter
set height(h) {
this._height = h;
}

// 对象方法
calcArea() {
return this._height * this._width;
}
}

Rectangle.xxx(); // 调用静态方法
let r = new Rectangle(100, 200); // 实例化
console.log(r.area); // 调用 setter
r.height = 200; // 调用 setter

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + " makes a noise.");
}
}

class Dog extends Animal { // 使用 extends 继承父类
speak() { // 重写父类的方法
super.speak(); // 调用父类的方法
console.log(this.name + " barks.");
}
}

常用内置对象及其方法

  1. Math 对象
  • Math.random() 生成一个小于 1 的随机数
    • Math.floor(float) 浮点数向下取整
    • Math.round(float) 浮点数四舍五入
    • Math.ceil(float) 浮点数向上取整
  1. JSON 对象

    • 作为从 JavaScript 发展出来的一种数据交换格式,JSON 当然少不了 JavaScript 本身的支持。

    • JSON.parse(jsonString) 将 JSON 字符串转换为一个对象并返回。(JSON 字符串举例:'{"name" : "Fido", "sex" : "female", "weight" : 37}',JSON 数组也可)

    • JSON.stringify(object) 将 JavaScript 对象转换为 JSON 字符串并返回。(JSON 格式不支持方法,所以被转换的 JavaScript 对象不能包含方法)

    • TODO

Promise、async 和 await

正则表达式

在 JavaScript 中,正则表达式被原生支持。

使用正则表达式匹配字符串:

  1. 创建正则表达式(RegExp)对象。

    1
    2
    3
    4
    5
    // 方式一:
    let phoneNumber = /^\d{3}-?\d{4}$/ // 开始和结尾的斜杠标志着这是一个正则表达式对象,搜索模式写在两斜杠之间。

    // 方式二:
    let phoneNumber = new RegExp(/^\d{3}-?\d{4}$/);
  2. 使用 string.match() 方法匹配字符串。

    1
    2
    let str = "555-1212";       // str 为待搜索的字符串
    let result = str.match(phoneNumber); // match 方法以一个 RegExp 对象为参数

可以在结尾斜杠后面加参数以控制匹配结果:

1
let regexp = /\w{3,10}and/gi

或者使用构造函数:

1
let regexp = new RegExp(/\w{3,10}and/, "gi");
  1. g 表示全局匹配(global),返回所有匹配的结果。如果没有 g,只会返回第一个匹配的结果。
  2. i 表示不区分大小写。

使用函数 RegExp() 可以将字符串转换为正则表达式:

1
2
3
let regexp1 = RegExp("and");            // 转换后为 /and/

let regexp2 = RegExp("and", "g"); // 转换后为 /and/g

异常处理

在 JavaScript 中异常处理是很重要的。

JavaScript 中使用 try-catch 处理异常,还可以使用 throw 抛出自定义的异常。

1
2
3
4
5
6
try {
// 可能出现错误的代码
}
catch(变量名) {
// 如果捕获了异常,会执行 catch 代码块中的语句
}

上例中抛出的是系统内置的异常,通常是一个对象,可以使用 变量名.message 调用错误信息。

下面是使用 throw 的例子:

1
2
3
4
5
6
7
8
9
10
try {
let x = theValue;
if(x == "") throw "empty"; // 抛出自定义的异常,这个异常是一个字符串
if(isNaN(x)) throw "not a number";
if(x > 10) throw "too high";
if(x < 5) throw "too low";
}
catch(err) {
console.log("Error: " + err + ".");
}