JavaScript高级程序设计(4)
第6章 集合引用类型
### 6.1 Object - 使用 new 操作符和 Object 构造函数 ```javascript let person = new Object(); person.name = "Nicholas"; person.age = 29; ``` - 使用对象字面量(object literal)表示法 ```javascript let person = { name: "Nicholas", age: 29, 5: true //属性名可以是字符串或数值 }; ``` - 用对象字面量表示法来定义一个只有默认属性和方法的对象,只要使用一对大括号,中间留空就行了: ```javascript let person = {}; // 与 new Object()相同 person.name = "Nicholas"; person.age = 29; ``` >注意 在使用对象字面量表示法定义对象时,并不会实际调用 Object 构造函数。 ### 6.2 Array ##### 6.2.1 创建数组 - 使用 Array 构造函数 ```javascript let colors = new Array(); let colors = new Array(20);//创建一个初始 length 为 20 的数组 let colors = new Array("red", "blue", "green"); //会创建一个包含 3 个字符串值的数组 ``` - 使用 Array 构造函数时,也可以省略 new 操作符 ```javascript let colors = Array(3); // 创建一个包含 3 个元素的数组 let names = Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组 ``` - 使用数组字面量(array literal)表示法 ```javascript let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组 let names = []; // 创建一个空数组 let values = [1,2,]; // 创建一个包含 2 个元素的数组 ``` >注意 与对象一样,在使用数组字面量表示法创建数组不会调用 Array 构造函数。 - Array.from() Array.from()用于将类数组结构转换为数组实例,是ES6 新增的用于创建数组的静态方法。第一个参数是一个类数组对象,即任何可迭代的结构,或者有一个 length 属性 和可索引元素的结构。还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像调用 Array.from().map()那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函数中 this 的值。但这个重写的 this 值在箭头函数中不适用。 ```javascript // 字符串会被拆分为单字符数组 console.log(Array.from("Matt")); // ["M", "a", "t", "t"] // 可以使用 from()将集合和映射转换为一个新数组 const m = new Map().set(1, 2) .set(3, 4); const s = new Set().add(1) .add(2) .add(3) .add(4); console.log(Array.from(m)); // [[1, 2], [3, 4]] console.log(Array.from(s)); // [1, 2, 3, 4] // Array.from()对现有数组执行浅复制 const a1 = [1, 2, 3, 4]; const a2 = Array.from(a1); // arguments 对象可以被轻松地转换为数组 function getArgsArray() { return Array.from(arguments); } console.log(getArgsArray(1, 2, 3, 4)); // [1, 2, 3, 4] // from()也能转换带有必要属性的自定义对象 const arrayLikeObject = { 0: 1, 1: 2, 2: 3, 3: 4, length: 4 }; console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4] ``` - Array.of() Array.of()可以把一组参数转换为数组。这个方法用于替代在 ES6之前常用的 Array.prototype.slice.call(arguments),一种异常笨拙的将 arguments 对象转换为数组的写法: ```javascript console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] console.log(Array.of(undefined)); // [undefined] ``` ##### 6.2.2 数组空位 - 可以像下面这样创建一个空位数组: ```javascript const options = [,,,,,]; // 创建包含 5 个元素的数组 console.log(options.length); // 5 console.log(options); // [,,,,,] ``` >注意 由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要空位,则可以显式地用 undefined 值代替 ##### 6.2.3 数组索引 - 要取得或设置数组的值,需要使用中括号并提供相应值的数字索引 ```javascript let colors = ["red", "blue", "green"]; // 定义一个字符串数组 alert(colors[0]); // 显示第一项 colors[2] = "black"; // 修改第三项 colors[3] = "brown"; // 添加第四项 ``` ##### 6.2.4 检测数组 - 在只有一个网页(因而只有一个全局作用域)的情况下,使用 instanceof 操作符就足矣: ```javascript if (value instanceof Array){ // 操作数组 } ``` - 如果网页里有多个框架,则可能涉及两个不同的全局执行上下文,就会有两个不同版本的 Array 构造函数,如果要把数组从一个框架传给另一个框架,则这个数组的构造函数将有别于在第二个框架内本地创建的数组。Array.isArray()方法就是解决这个问题。 ```javascript if (Array.isArray(value)){ // 操作数组 } ``` ##### 6.2.5 迭代器方法 - `array.keys():` 返回数组的索引迭代器 - `array.values():`返回数组值的迭代器 - `array.entries():` 返回数组键值对的迭代器 ```javascript let arr = ['a','b','c']; console.log(Array.from(arr.keys())) //[ 0, 1, 2 ] console.log(Array.from(arr.values())) //[ 'a', 'b', 'c' ] console.log(Array.from(arr.entries())) //[ [ 0, 'a' ], [ 1, 'b' ], [ 2, 'c' ] ] ``` ##### 6.2.6 复制和填充方法 - 复制方法: `copyWithin()` - 填充方法: `fill()` ```javascript let arr = ['a','b','c']; arr.fill(1) // [1,1,1] 进行填充 arr.copyWithin(0,2) //[ 'a', 'b', 'c' ] 浅复制索引为2的元素到索引为0的位置上 ``` ##### 6.2.7 转换方法 - `valueOf()`返回的还是数组本身。 - `toString()`返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。 - `toLocaleString()`方法也可能返回跟 toString()和 valueOf()相同的结果,但也不一定。 - `join()`方法接收一个参数,即字符串分隔符,返回包含所有项的字符串。来 ```javascript let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组 alert(colors.toString()); // red,blue,green alert(colors.valueOf()); // red,blue,green alert(colors); // red,blue,green join(",")); // red,green,blue ``` ##### 6.2.8 栈方法 >栈是一种后进先出(LIFO,Last-In-First-Out)的结构,也就 是最近添加的项先被删除。数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个 地方发生,即栈顶。ECMAScript 数组提供了 push()和 pop()方法,以实现类似栈的行为。 - `push()`方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。 - `pop()`方法则用于删除数组的最后一项,同时减少数组的 length 值,返回被删除的项。 ```javascript let colors = new Array(); // 创建一个数组 let count = colors.push("red", "green"); // 推入两项 alert(count); // 2 count = colors.push("black"); // 再推入一项 alert(count); // 3 let item = colors.pop(); // 取得最后一项 alert(item); // black alert(colors.length); // 2 ``` ##### 6.2.9 队列方法 - 队列以先进先出(FIFO,First-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。 - `shift()`:会删除数组的第一项并返回它,然后数组长度减 1。使用 shift()和 push(),可以把数组当成队列来使用: ```javascript let colors = new Array(); // 创建一个数组 let count = colors.push("red", "green"); // 推入两项 alert(count); // 2 count = colors.push("black"); // 再推入一项 alert(count); // 3 let item = colors.shift(); // 取得第一项 alert(item); // red alert(colors.length); // 2 ``` - `unshift():`在数组开头添加任意多个值,然后返回新的数组长度。通过使用 unshift()和 pop(),可以在相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据,如下例所示: ```javascript let colors = new Array(); // 创建一个数组 let count = colors.unshift("red", "green"); // 从数组开头推入两项 alert(count); // 2 count = colors.unshift("black"); // 再推入一项 alert(count); // 3 let item = colors.pop(); // 取得最后一项 alert(item); // green alert(colors.length); // 2 ``` ##### 6.2.10 排序方法 - sort():排序,默认是正向的,可以接受一个函数用于倒叙排序 - reverse():倒序排序 ```javascript let arr = [1,3,2,] console.log( arr.sort(), // 正序排序 arr.reverse(), // 倒序排序 arr.sort((a,b) => { // 使用 sort 函数倒序排序 return b-a }) ) ``` ##### 6.2.11 操作方法 - `contact():`将参数的数组搞到现有数组的后面,返回一个新数组,新数组是对原数组的浅拷贝。 - s`lice(a,b):`返回数组索引从 a 开始到 b (不包含 b)的数组元素。如果 b 不传则默认是数组最后 - `splice(a,b,...c) :`这个函数比较吊,删除、插入、替换都能搞 a 开始的索引 b 要删除的数量 c 要插入的元素(可以有多个) ```javascript // contact() 测试 let obj = {}; let arr = [obj,3,2,] let b = arr.concat(['']) console.log(b) //[ {}, 3, 2, '' ] console.log(b[0] === arr[0]) //true //slice() 测试 let sArr = [1,2,3,4,5]; console.log(sArr.slice(2)) // [ 3, 4, 5 ] //splice() 测试 let sArr1 = [1,2,3,4,5]; sArr1.splice(0,2) // 删除:index = 0 1 的元素 console.log(sArr1); // [ 3, 4, 5 ] sArr1.splice(0,0,'red') //插入:从index=0开始(第二个0是说要删除的元素数量是0),插入 red console.log(sArr1); //[ 'red', 3, 4, 5 ] sArr1.splice(0,1,'blue') // 替换: 从index=0开始,干掉1个元素,然后插入 blue console.log(sArr1); //[ 'blue', 3, 4, 5 ] ``` ##### 6.2.12 搜索与位置方法 - 严格相等 :indexOf() lastIndexOf() includes()会使用 === 对比参数与数组的每个元素 - 断言函数 :find() findIndex()接收一个函数用于判断是否相等。注意:这俩函数找到第一个结果后就不找了。 ```javascript let arr = [1,'a','a','b',5,{key: 'value'}] console.log( arr.indexOf('b'), // 3 arr.lastIndexOf('a'), // 2 arr.includes('44'), // false arr.find((element,index,arr) => { // {key: 'value'} 断言函数 return element?.key === 'value' }), arr.findIndex((element,index,arr) => { // 断言函数 return element?.key === 'value' // 5 }) ) ``` ##### 6.2.13 迭代方法 - `every():`对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。 filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。 - `forEach():`对数组每一项都运行传入的函数,没有返回值。 - `map():`对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。 - `some():`对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。 这些方法都不改变调用它们的数组。在这些方法中,every()和 some()是最相似的,都是从数组中搜索符合某个条件的元素。对 every()来说,传入的函数必须对每一项都返回 true,它才会返回 true;否则,它就返回 false。而对 some()来说,只要有一项让传入的函数返回 true,它就会返回 true。 ```javascript let arr = [1,'a','a','b',5,{key: 'value'}] console.log( arr.every((element,index,arr) => {return element.key}), // false 每个都符合才会 true arr.some((element,index,arr) => {return element.key}), // true 一个符合就会 true arr.filter((element,index,arr) => {return element.key}), // 过滤操作 返回符合的数组 arr.map((element,index,arr) => {return element.toString()}), // 转化操作 返回转化后的数组 ) arr.forEach((value,index,array) => {console.log(value)}) // 遍历 没有返回值 ``` ##### 6.2.14 归并方法 - `reduce() `从左向右归并 - `recudeRight() `从右向左归并 ```javascript let values = [1, 2, 3, 4, 5]; let sum = values.reduce((prev, cur, index, array) => prev + cur); alert(sum); // 15 ``` ### 6.3 定型数组 >定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率。实际上,JavaScript 并没有“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。 ##### 6.3.1 历史 WebGL 的早期版本中,因为 JavaScript 数组与原生数组之间不匹配,所以出现了性能问题。,Mozilla 为解决这个问题而实现了 CanvasFloatArray。最终,CanvasFloatArray变成了 Float32Array,也就是今天定型数组中可用的第一个“类型”。 ##### 6.3.2 ArrayBuffer Float32Array 实际上是一种“视图”,可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。 ```javascript const buf = new ArrayBuffer(16); // 在内存中分配 16 字节 alert(buf.byteLength); // 16 const buf2 = buf.slice(4, 12); //使用 slice()复制其全部或部分到一个新 实例中 alert(buf2.byteLength); // 8 ``` ##### 6.3.3 DataView 第一种允许你读写 ArrayBuffer 的视图是 DataView。这个视图专为文件 I/O 和网络 I/O 设计,其API 支持对缓冲数据的高度控制,但相比于其他类型的视图性能也差一些。DataView 对缓冲内容没有任何预设,也不能迭代。 ```javascript const buf = new ArrayBuffer(16); // DataView 默认使用整个 ArrayBuffer const fullDataView = new DataView(buf); alert(fullDataView.byteOffset); // 0 alert(fullDataView.byteLength); // 16 alert(fullDataView.buffer === buf); // true ``` ##### 6.3.4 定型数组 定型数组是另一种形式的 ArrayBuffer 视图。设计定型数组的目的就是提高与 WebGL 等原生库交换二进制数据的效率。 创建定型数组的方式包括读取已有的缓冲、使用自有缓冲、填充可迭代结构,以及填充基于任意类型的定型数组。另外,通过<ElementType>.from()和<ElementType>.of()也可以创建定型数组: ```javascript // 创建一个 12 字节的缓冲 const buf = new ArrayBuffer(12); // 创建一个引用该缓冲的 Int32Array const ints = new Int32Array(buf); // 这个定型数组知道自己的每个元素需要 4 字节 // 因此长度为 3 alert(ints.length); // 3 ``` ### 6.4 Map >作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机制。Map 的大多数特性都可以通过 Object 类型实现。 ##### 6.4.1 基本 API - 使用 new 关键字和 Map 构造函数可以创建一个空映射: ```javascript const m = new Map(); ``` - 可以给 Map 构造函数传入一个可迭代对象,需要包含键/值对数 组。 ```javascript // 使用嵌套数组初始化映射 const m1 = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); alert(m1.size); // 3 // 使用自定义迭代器初始化映射 const m2 = new Map({ [Symbol.iterator]: function*() { yield ["key1", "val1"]; yield ["key2", "val2"]; yield ["key3", "val3"]; } }); alert(m2.size); // 3 // 映射期待的键/值对,无论是否提供 const m3 = new Map([[]]); alert(m3.has(undefined)); // true alert(m3.get(undefined)); // undefined ``` - 可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可 以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。 ```javascript const m = new Map(); alert(m.has("firstName")); // false alert(m.get("firstName")); // undefined alert(m.size); // 0 m.set("firstName", "Matt") .set("lastName", "Frisbie"); m.delete("firstName"); // 只删除这一个键/值对 m.clear(); // 清除这个映射实例中的所有键/值对 ``` - set()方法返回映射实例,因此可以把多个操作连缀起来,包括初始化声明: ```javascript const m = new Map().set("key1", "val1"); ``` - 与 Object 只能使用数值、字符串或符号作为键不同,Map 可以使用任何 JavaScript 数据类型作为键。 ```javascript const m = new Map(); const functionKey = function() {}; const symbolKey = Symbol(); const objectKey = new Object(); m.set(functionKey, "functionValue"); m.set(symbolKey, "symbolValue"); m.set(objectKey, "objectValue"); ``` ##### 6.4.2 顺序与迭代 与 Object 类型的一个主要差异是,Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器: ```javascript const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); alert(m.entries === m[Symbol.iterator]); // true ``` ##### 6.4.3 选择 Object 还是 Map 对于多数 Web 开发任务来说,选择 Object 还是 Map 只是个人偏好问题,影响不大。不过,对于在乎内存和性能的开发者来说,对象和映射之间确实存在显著的差别。 1. 内存占用。不同浏览器的情况不同,但给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。 2. 插入性能。向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入 Map 在所有浏览器中一般会稍微快一点儿。 3. 查找速度。与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对,则 Object 有时候速度更快。 4. 删除性能。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。 ### 6.5 WeakMap >ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型,为这门语言带来了增强的键/值对存储机制。WeakMap 是 Map 的“兄弟”类型,其 API 也是 Map 的子集。WeakMap 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱映射”中键的方式。 ##### 6.5.1 基本 API - 可以使用 new 关键字实例化一个空的 WeakMap: ```javascript const wm = new WeakMap(); ``` - 弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。 ```javascript const key1 = {id: 1}, key2 = {id: 2}, key3 = {id: 3}; // 使用嵌套数组初始化弱映射 const wm1 = new WeakMap([ [key1, "val1"], [key2, "val2"], [key3, "val3"] ]); ``` - 初始化之后可以使用 set()再添加键/值对,可以使用 get()和 has()查询,还可以使用 delete()删除: ```javascript const wm = new WeakMap(); const key1 = {id: 1}, key2 = {id: 2}; alert(wm.has(key1)); // false alert(wm.get(key1)); // undefined wm.set(key1, "Matt") .set(key2, "Frisbie"); wm.delete(key1); // 只删除这一个键/值对 ``` ##### 6.5.2 弱键 WeakMap 中“weak”表示弱映射的键是“弱弱地拿着”的。意思就是,这些键不属于正式的引用,不会阻止垃圾回收。但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。 ##### 6.5.3 不可迭代键 因为 WeakMap 中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力。当然,也用不着像 clear()这样一次性销毁所有键/值的方法。 ### 6.6 Set ##### 6.6.1 基本 API - 使用 new 关键字和 Set 构造函数可以创建一个空集合: ```javascript const m = new Set(); ``` - 如果想在创建的同时初始化实例,则可以给 Set 构造函数传入一个可迭代对象,其中需要包含插入到新集合实例中的元素: ```javascript // 使用数组初始化集合 const s1 = new Set(["val1", "val2", "val3"]); alert(s1.size); // 3 // 使用自定义迭代器初始化集合 const s2 = new Set({ [Symbol.iterator]: function*() { yield "val1"; yield "val2"; yield "val3"; } }); alert(s2.size); // 3 ``` - 初始化之后,可以使用 add()增加值,使用 has()查询,通过 size 取得元素数量,以及使用 delete()和 clear()删除元素 ```javascript const s = new Set(); alert(s.has("Matt")); // false alert(s.size); // 0 s.add("Matt") .add("Frisbie"); s.delete("Matt"); s.clear(); // 销毁集合实例中的所有值 ``` - 与 Map 类似,Set 可以包含任何 JavaScript 数据类型作为值。 ```javascript const s = new Set(); const functionVal = function() {}; const symbolVal = Symbol(); const objectVal = new Object(); s.add(functionVal); s.add(symbolVal); s.add(objectVal); ``` ##### 6.6.2 顺序与迭代 Set 会维护值插入时的顺序,因此支持按顺序迭代。集合实例可以提供一个迭代器(Iterator),能以插入顺序生成集合内容。可以通过 values()方法及其别名方法 keys()(或者 Symbol.iterator 属性,它引用 values())取得这个迭代器: ```javascript const s = new Set(["val1", "val2", "val3"]); alert(s.values === s[Symbol.iterator]); // true alert(s.keys === s[Symbol.iterator]); // true for (let value of s.values()) { alert(value); } ``` ##### 6.6.3 定义正式集合操作 从各方面来看,Set 跟 Map 都很相似,只是 API 稍有调整。唯一需要强调的就是集合的 API 对自身的简单操作。很多开发者都喜欢使用 Set 操作,但需要手动实现:或者是子类化 Set,或者是定义一个实用函数库。 ### 6.7 WeakSet ECMAScript 6 新增的“弱集合”(WeakSet)是一种新的集合类型,为这门语言带来了集合数据结构。WeakSet 是 Set 的“兄弟”类型,其 API 也是 Set 的子集。WeakSet 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱集合”中值的方式。 ##### 6.7.1 基本 API - 可以使用 new 关键字实例化一个空的 WeakSet: ```javascript const ws = new WeakSet(); ``` - 弱集合中的值只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置值会抛出 TypeError。 ```javascript const val1 = {id: 1}, val2 = {id: 2}, val3 = {id: 3}; // 使用数组初始化弱集合 const ws1 = new WeakSet([val1, val2, val3]); alert(ws1.has(val1)); // true alert(ws1.has(val2)); // true alert(ws1.has(val3)); // true ``` ##### 6.7.2 弱值 WeakSet 中“weak”表示弱集合的值是“弱弱地拿着”的。意思就是,这些值不属于正式的引用,不会阻止垃圾回收。 ##### 6.7.3 不可迭代值 因为 WeakSet 中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力。当然,也用不着像 clear()这样一次性销毁所有值的方法。 ##### 6.7.4 使用弱集合 相比于 WeakMap 实例,WeakSet 实例的用处没有那么大。不过,弱集合在给对象打标签时还是有价值的。来看下面的例子,这里使用了一个普通 Set: ```javascript const disabledElements = new Set(); const loginButton = document.querySelector('#login'); // 通过加入对应集合,给这个节点打上“禁用”标签 disabledElements.add(loginButton); ``` ### 6.8 迭代与扩展操作 - 有 4 种原生集合类型定义了默认迭代器: - Array - 所有定型数组 - Map - Set - 这意味着上述所有类型都支持顺序迭代,都可以传入 for-of 循环: ```javascript let iterableThings = [ Array.of(1, 2), typedArr = Int16Array.of(3, 4), new Map([[5, 6], [7, 8]]), new Set([9, 10]) ]; for (const iterableThing of iterableThings) { for (const x of iterableThing) { console.log(x); } } ``` - 这也意味着所有这些类型都兼容扩展操作符。扩展操作符在对可迭代对象执行浅复制时特别有用,只需简单的语法就可以复制整个对象: ```javascript let arr1 = [1, 2, 3]; let arr2 = [...arr1]; console.log(arr1); // [1, 2, 3] console.log(arr2); // [1, 2, 3] console.log(arr1 === arr2); // false ```
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket