💙《JavaScript 高级程序与设计》 | 集合引用类型

158 阅读9分钟

6.1 Object

创建 Object 的实例有两种方式:

  • 使用 new 操作符和 Object 构造函数;

    let person = new Object(); 
    person.name = "Nicholas"; 
    person.age = 29;
    
  • 使用对象字面量(object literal)表示法;

    let person = { 
     name: "Nicholas", 
     age: 29 
    };
    

6.2 Array

创建数组的方式:

  • 使用 Array 构造函数:let colors = new Array();
  • 使用数组字面量(array literal)表示法:let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组
  • from(): 用于将类数组结构转换为数组实例。第一个参数是一个类数组对象,第二个可选的映射函数参数,第三个可选参数,用于指定映射函数中 this 的值。console.log(Array.from("Matt")); // ["M", "a", "t", "t"]
  • of(): 用于将一组参数转换为数组实例;这个方法用于替代在 ES6 之前常用的 Array.prototype.slice.call(arguments) console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]

检测数组的方式

  • instanceof 操作符:value instanceof Array
  • Array.isArray() 方法:Array.isArray(value)

检索数组内容的方法:

  • **keys():**返回数组索引的迭代器
  • **values():**返回数组元素的迭代器
  • **entries():**返回索引/值对的迭代器

ES6 新增了两个方法:批量复制方法 copyWithin(),以及填充数组方法 fill()。

  • fill():向一个已有的数组中插入全部或部分相同的值;开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。

    const zeroes = [0, 0, 0, 0, 0]; 
    // 用 5 填充整个数组
    zeroes.fill(5); 
    console.log(zeroes); // [5, 5, 5, 5, 5]
    // 用 7 填充索引大于等于 1 且小于 3 的元素
    zeroes.fill(7, 1, 3); 
    console.log(zeroes); // [0, 7, 7, 0, 0]; 
    zeroes.fill(0); // 重置
    
  • copyWithin():按照指定范围浅复制数组中的部分内容,插入到指定索引开始的位置。

    let ints, 
     reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 
    reset();
    // 从 ints 中复制索引 0 开始的内容,插入到索引 5 开始的位置
    // 在源索引或目标索引到达数组边界时停止
    ints.copyWithin(5); 
    console.log(ints); // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] 
    reset(); 
    // 从 ints 中复制索引 5 开始的内容,插入到索引 0 开始的位置
    ints.copyWithin(0, 5); 
    console.log(ints); // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]
    

转换方法:

  • toLocaleString():返回数组本身;
  • toString():返回以逗号分隔开值的字符串
  • valueOf():返回数组本身;
  • join():接收一个参数,即字符串分隔符,返回包含所有项的字符串;如果不传入任何参数,或者传入 undefined,则仍然使用逗号作为分隔符。

如果数组中某一项是 nullundefined,则在 join()toLocaleString()toString()valueOf() 返回的结果中会以空字符串表示。

栈方法

栈方法是一种先进后出的结构,最近添加的项先被删除。数据项的插入和删除只在栈的一个地方发生,即栈顶。ECMAScript 数组提供了 push()pop() 方法,以实现类似栈的行为。

  • push():接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度;

  • pop():用于删除数组的最后一项,同时减少数组的 length 值,返回被删除的项。

    let colors = ["red", "blue"]; 
    colors.push("brown"); // 再添加一项
    colors[3] = "black"; // 添加一项
    alert(colors.length); // 4 
    let item = colors.pop(); // 取得最后一项
    alert(item); // black
    

队列方法

队列以先进先出(FIFO,First-In-First-Out)形式限制访问;

  • shift():删除数组第一项并返回它,然后数组长度减 1;
  • unshift():在数组开头添加任意多个值,然后返回新的数组长度;

排序方法

数组有两个方法可以用来对元素重新排序:reverse()sort()

  • reverse():将数组元素反向排列;

    let values = [1, 2, 3, 4, 5]; 
    values.reverse(); 
    alert(values); // 5,4,3,2,1
    
  • sort():默认情况下,sort() 会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。sort() 方法可以接收一个比较函数,用于判断哪个值应该排在前面。

    let values = [0, 1, 5, 10, 15]; 
    values.sort((a, b) => a < b ? 1 : a > b ? -1 : 0); 
    alert(values); // 15,10,5,1,0
    

操作方法

  • slice():用于创建一个包含原有数组中一个或多个元素的新数组;接收一个或两个参数:返回元素的开始索引和结束索引。如果只有一个参数,则 slice() 会返回该索引到数组末尾的所有元素。如果有两个参数,则 slice() 返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素。记住,这个操作不影响原始数组。

    let colors = ["red", "green", "blue", "yellow", "purple"]; 
    let colors2 = colors.slice(1); 
    let colors3 = colors.slice(1, 4); 
    alert(colors2); // green,blue,yellow,purple 
    alert(colors3); // green,blue,yellow
    
  • splice():可以对元素进行插入删除替换

    • 插入:需要给 splice() 传 3 个参数:开始位置、0(要删除的元素数量)和要插入的元素,可以在数组中指定的位置插入元素;

    • 删除:需要给 splice() 传 2 个参数:要删除的第一个元素的位置和要删除的元素数量。

    • 替换:在删除元素的同时可以在指定位置插入新元素,同样要传入 3 个参数:开始位置、要删除元素的数量和要插入的任意多个元素。

      let colors = ["red", "green", "blue"]; 
      let removed = colors.splice(0,1); // 删除第一项
      alert(colors); // green,blue 
      alert(removed); // red,只有一个元素的数组
      removed = colors.splice(1, 0, "yellow", "orange"); // 在位置 1 插入两个元素
      alert(colors); // green,yellow,orange,blue 
      alert(removed); // 空数组
      removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
      alert(colors); // green,red,purple,orange,blue 
      alert(removed); // yellow,只有一个元素的数组
      

搜索和位置方法

搜索数组的方法:按严格相等搜索和按断言函数搜索

严格相等:

  • indexOf() :起始位置开始查找,返回要查找的元素在数组中的位置,如果没找到则返回1。
  • lastIndexOf() :数组末尾(最后一项)开始向前搜索,返回要查找的元素在数组中的位置,如果没找到则返回1。
  • includes() :返回布尔值,表示是否至少找到一个与指定元素匹配的项。

断言函数:

  • find() :返回第一个匹配的元素;

  • findIndex() :返回第一个匹配的元素的索引;

    ```js
    const people = [  {  name: "Matt",  age: 27  },  {  name: "Nicholas",  age: 29  } ]; 
    alert(people.find((element, index, array) => element.age < 28)); 
    // {name: "Matt", age: 27} 
    alert(people.findIndex((element, index, array) => element.age < 28)); 
    // 0
    ```
    

迭代方法

  • every() :如果对每一项函数都返回 true,则这个方法返回 true
  • filter():函数返回 true 的项会组成数组之后返回;
  • forEach():对数组每一项都运行传入的函数,没有返回值;
  • map():返回由每次函数调用的结果构成的数组;
  • some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。这些方法都不改变调用它们的数组。
  • every()some() 是最相似的,都是从数组中搜索符合某个条件的元素。对 every() 来说,传入的函数必须对每一项都返回 true,它才会返回 true;否则,它就返回 false。而对 some() 来说,只要有一项让传入的函数返回 true,它就会返回 true

归并方法

  • reduce():从数组第一项开始遍历到最后一项
  • reduceRight():从最后一项开始遍历至第一项

传给 reduce()和 reduceRight()的的函数接收 4 个参数:上一个归并值、当前项、当前项的索引和数组本身。

// reduce()
let values = [1, 2, 3, 4, 5]; 
let sum = values.reduce((prev, cur, index, array) => prev + cur); 
alert(sum); // 15

// reduceRight()
let values = [1, 2, 3, 4, 5]; 
let sum = values.reduceRight(function(prev, cur, index, array){ 
 return prev + cur; 
}); 
alert(sum); // 15

6.3 定型数组

定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率。

ArrayBuffer

ArrayBuffer 是所有定型数组及视图引用的基本单位,可用于在内存中分配特定数量的字节空间。

const buf = new ArrayBuffer(16); // 在内存中分配 16 字节
alert(buf.byteLength); // 16

ArrayBuffer 一经创建就不能再调整大小。不过,可以使用 slice()复制其全部或部分到一个新 实例中:

const buf1 = new ArrayBuffer(16); 
const buf2 = buf1.slice(4, 12); 
alert(buf2.byteLength); // 8

通过声明 ArrayBuffer 分配的堆内存可以被当成垃圾回收,不用手动释放。

第一种允许你读写 ArrayBuffer 的视图是 DataView

6.4 Map

Map 是一种新的集合类型;

使用 new 关键字和 Map 构造函数可以创建一个空映射:const m = new Map();

可以使用 set() 方法再添加键/值对,使用 get()has() 进行查询,可以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()clear() 删除值。

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"); 
alert(m.has("firstName")); // true 
alert(m.get("firstName")); // Matt 
alert(m.size); // 2 
m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false 
alert(m.has("lastName")); // true 
alert(m.size); // 1 
m.clear(); // 清除这个映射实例中的所有键/值对
alert(m.has("firstName")); // false 
alert(m.has("lastName")); // false 
alert(m.size); // 0

选择 Object 还是 Map

  • 内存占用:Map 大约可以比 Object 多存储 50% 的键/值对;
  • 插入性能:插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操作,那么显然 Map 的性能更佳
  • 查找速度:如果只包含少量键/值对,则 Object 有时候速度更快;查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择 Object 更好一些;
  • 删除性能:Mapdelete() 操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。

6.5 WeakMap

WeakMapMap 的“兄弟”类型,其 API 也是 Map 的子集。

可以使用 new 关键字实例化一个空的 WeakMapconst wm = new WeakMap();

可以使用 set() 再添加键/值对,可以使用 get()has() 查询,还可以使用 delete() 删除

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"); 
alert(wm.has(key1)); // true 
alert(wm.get(key1)); // Matt 
wm.delete(key1); // 只删除这一个键/值对
alert(wm.has(key1)); // false 
alert(wm.has(key2)); // true

6.6 Set

Set 是一种新集合类型

使用 new 关键字和 Set 构造函数可以创建一个空集合:const m = new Set();

可以使用 add() 增加值,使用 has() 查询,通过 size 取得元素数量,以及使用 delete()clear() 删除元素;

const s = new Set(); 
alert(s.has("Matt")); // false 
alert(s.size); // 0 
s.add("Matt") 
 .add("Frisbie"); 
alert(s.has("Matt")); // true 
alert(s.size); // 2 
s.delete("Matt"); 
alert(s.has("Matt")); // false 
alert(s.has("Frisbie")); // true 
alert(s.size); // 1 
s.clear(); // 销毁集合实例中的所有值
alert(s.has("Matt")); // false 
alert(s.has("Frisbie")); // false 
alert(s.size); // 0

add()delete() 操作是幂等的。

  • add():返回集合的实例,所以可以将多个添加操作连缀起来;
  • delete():返回一个布尔值,表示集合中是否存在要删除的值;

values() 是默认迭代器,可以直接对集合实例使用扩展操作,把集合转换为数组:

const s = new Set(["val1", "val2", "val3"]); 
console.log([...s]); // ["val1", "val2", "val3"]

集合的 entries() 方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现:

const s = new Set(["val1", "val2", "val3"]); 
for (let pair of s.entries()) { 
 console.log(pair); 
} 
// ["val1", "val1"] 
// ["val2", "val2"] 
// ["val3", "val3"]

如果不使用迭代器,而是使用回调方式,则可以调用集合的 forEach()方法并传入回调,依次迭代每个键/值对。

6.7 WeakSet

“弱集合”(WeakSet)是一种新的集合类型。

可以使用 new 关键字实例化一个空的 WeakSetconst ws = new WeakSet();

弱集合中的值只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置值会抛出 TypeError

可以使用 add() 再添加新值,可以使用 has() 查询,还可以使用 delete() 删除;

add() 方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明:

const val1 = {id: 1}, 
 val2 = {id: 2}, 
 val3 = {id: 3}; 
const ws = new WeakSet().add(val1); 
ws.add(val2) 
 .add(val3);

6.8 迭代与扩展操作

4 种原生集合类型定义了默认迭代器:

  • Array
  • 所有定型数组
  • Map
  • Set

上述所有类型都支持顺序迭代,都可以传入 for-of 循环,都兼容扩展操作符,支持多种构建方法,比如 Array.of()Array.from() 静态方法