JavaScript高级程序设计第四版学习--第六章

本文深入探讨JavaScript中的对象、数组、定型数组、Map、WeakMap、Set、WeakSet等核心数据结构,涵盖创建、操作及应用场景等内容,并对比不同数据结构间的性能差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


title: JavaScript高级程序设计第四版学习–第六章
date: 2021-5-18 22:29:20
author: Xilong88
tags: JavaScript

本章内容
对象
数组与定型数组
Map 、WeakMap 、Set 以及WeakSet 类型
可能出现的面试题:
1.数组的操作的程序题
2.了解过定型数组吗?
3.了解过映射吗?了解过弱映射吗?两者差别
4.了解过集合吗?了解过弱集合吗?两者差别
5.集合和映射的差别?
6.集合映射的程序题
7.对象和映射的内存,性能问题

总结:
主要内容是简单介绍对象,介绍数组和定型数组,介绍映射和集合,这些知识都比较常用,感觉不仅仅工作中会用到,做算法也会用到很多,所以要对操作很熟悉。

知识点:

1.创建对象:
(1)通过new + Object构造函数

let person = new Object();
person.name = "Nicholas";
person.age = 29;

(2)通过对象字面量

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

//属性名可以是字符串或数值,数值会自动转化为字符串
//通过字面量方法构造不会调用Object的构造函数

(3)用对象当参数时,一般把必选参数用命名参数,对象参数用来做为可选参数的封装集合。

属性访问可以用 对象名.属性名,也可以对象名[属性名],假如属性名称有空格,只能用后者

2.Array

创建数组:

(1)通过new+构造函数:

let colors = new Array();

(2)通过构造函数加数组大小:

let colors = new Array(20);

(3)通过构造函数加字面量元素:

let colors = new Array("red", "blue", "green");

(4)以上的new 省略效果一样

(5) 直接用数组字面量:

let colors = ["red", "blue", "green"];  // 创建一个包含3个元素的数组
let names = [];                         // 创建一个空数组
let values = [1,2,];                    // 创建一个包含2个元素的数组

(6)Array.from()和Array.of()可以创建数组:

// 字符串会被拆分为单字符数组
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);
console.log(a1);        // [1, 2, 3, 4]
alert(a1 === a2); // false
// 可以使用任何可迭代对象
const iter = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;

  }
};
console.log(Array.from(iter)); // [1, 2, 3, 4]
// 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]

form的第二个参数是一个方法,用于处理每一个迭代元素:

const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);
const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});
console.log(a2);  // [1, 4, 9, 16]
console.log(a3);  // [1, 4, 9, 16]

原本说第三参数可以改变映射函数this的指向,但是我尝试了不行,网上也没查到资料,这里后面再研究研究。

Array.of() 可以把一组参数转换为数组。这个方法用于替代在ES6之
前常用的Array.prototype.slice.call(arguments) ,一种异常笨
拙的将arguments 对象转换为数组的写法:

console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(undefined));  // [undefined]

数组里可以存在空位,如:

const options = [,,,,,]; // 创建包含5个元素的数组
console.log(options.length);   // 5
console.log(options);          // [,,,,,]

ES6新增方法普遍将这些空位当成存在的元素,只不过值
为undefined

const options = [1,,,,5];
for (const option of options) {
  console.log(option === undefined);
}
// false
// true
// true
// true
// false
const a = Array.from([,,,]); // 使用ES6的Array.from()创建的包含3个空位的数组
for (const val of a) {
  alert(val === undefined);
}
// true
// true
// true
alert(Array.of(...[,,,])); // [undefined, undefined, undefined]
for (const [index, value] of options.entries()) {
  alert(value);
}
// 1
// undefined
// undefined
// undefined

map方法会跳过空位,join会把空位看成空字符串

const options = [1,,,,5];
// map()会跳过空位置
console.log(options.map(() => 6));  // [6, undefined, undefined, undefined, 6]
// join()视空位置为空字符串
console.log(options.join('-'));     // "1----5"

由于行为不一致和存在性能隐患,因此实践中要避免使用
数组空位。如果确实需要空位,则可以显式地用undefined 值代
替。

以下方式可以在数组的最后添加元素:

colors[colors.length] = xxx;

数组最多可以包含4 294 967 295个元素

越界会报undefined

通过修改length的大小可以删除数组后的元素

Array.isArray()方法检测一个对象是不是数组,因为不同执行上下文中的

value instanceof Array

结果可能不同,isArray其实检测了对象原型的构造函数中有没有Array字符串。

keys() 、values() 和entries()

const a = ["foo", "bar", "baz", "qux"];
// 因为这些方法都返回迭代器,所以可以将它们的内容
// 通过Array.from()直接转换为数组实例
const aKeys = Array.from(a.keys());
const aValues = Array.from(a.values());
const aEntries = Array.from(a.entries());
console.log(aKeys);     // [0, 1, 2, 3]
console.log(aValues);   // ["foo", "bar", "baz", "qux"]
console.log(aEntries);  // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]

fill方法可以复制:

const zeroes = [0, 0, 0, 0, 0];
// 用5填充整个数组
zeroes.fill(5);
console.log(zeroes);  // [5, 5, 5, 5, 5]
zeroes.fill(0);       // 重置
// 用6填充索引大于等于3的元素
zeroes.fill(6, 3);
console.log(zeroes);  // [0, 0, 0, 6, 6]
zeroes.fill(0);       // 重置
// 用7填充索引大于等于1且小于3的元素
zeroes.fill(7, 1, 3);
console.log(zeroes);  // [0, 7, 7, 0, 0];
zeroes.fill(0);       // 重置
// 用8填充索引大于等于1且小于4的元素
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1);
console.log(zeroes);  // [0, 8, 8, 8, 0];

fill() 静默忽略超出数组边界、零长度及方向相反的索引范围:

const zeroes = [0, 0, 0, 0, 0];
// 索引过低,忽略
zeroes.fill(1, -10, -6);
console.log(zeroes);  // [0, 0, 0, 0, 0]
// 索引过高,忽略
zeroes.fill(1, 10, 15);

console.log(zeroes);  // [0, 0, 0, 0, 0]
// 索引反向,忽略
zeroes.fill(2, 4, 2);
console.log(zeroes);  // [0, 0, 0, 0, 0]
// 索引部分可用,填充可用部分
zeroes.fill(4, 3, 10)
console.log(zeroes);  // [0, 0, 0, 4, 4]

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]
reset();
// 从ints中复制索引0开始到索引3结束的内容
// 插入到索引4开始的位置
ints.copyWithin(4, 0, 3);
alert(ints);  // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset();
// JavaScript引擎在插值前会完整复制范围内的值
// 因此复制期间不存在重写的风险
ints.copyWithin(2, 0, 6);
alert(ints);  // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset();
// 支持负索引值,与fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4, -7, -3);
alert(ints);  // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6]

copyWithin() 静默忽略超出数组边界、零长度及方向相反的索引范
围:

let ints,
    reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引过低,忽略
ints.copyWithin(1, -15, -12);
alert(ints);  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引过高,忽略
ints.copyWithin(1, 12, 15);
alert(ints);  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引反向,忽略
ints.copyWithin(2, 4, 2);
alert(ints);  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引部分可用,复制、填充可用部分
ints.copyWithin(4, 7, 10)
alert(ints);  // [0, 1, 2, 3, 7, 8, 9, 7, 8, 9];

转换方法:

toLocaleString() 、toString() 和
valueOf() 方法。

let colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组
alert(colors.toString());   // red,blue,green
alert(colors.valueOf());    // red,blue,green
alert(colors);              // red,blue,green

toLocaleString()
为了得到最终的字符串,会调用数组每个值的toLocaleString() 方
法,而不是toString() 方法

let person1 = {
  toLocaleString() {
    return "Nikolaos";
  },
  toString() {
    return "Nicholas";
  }
};
let person2 = {
  toLocaleString() {
    return "Grigorios";
  },
  toString() {
    return "Greg";
  }
};
let people = [person1, person2];
alert(people);                   // Nicholas,Greg
alert(people.toString());        // Nicholas,Greg
alert(people.toLocaleString());  // Nikolaos,Grigorios

join() 方法接收一个参数,即字符串分隔符,返回包含所有项的
字符串。来看下面的例子:

let colors = ["red", "green", "blue"];
alert(colors.join(","));     // red,green,blue
alert(colors.join("||"));    // red||green||blue

栈方法:

push返回改变后元素个数
pop返回弹出的元素

队列方法:

shift,push,unshift,pop

shift会返回弹出的元素

unshift可以从队首插入元素,返回元素个数

排序:

reverse可以反向排列元素
sort不带参数按照字符的编码顺序排列

如:5在15后面,因为5的编码大于1的编码

sort可以传入一个函数,对数组元素进行比较,如果返回值为负表示a在b前,正表示a在b后,
0表示相等。

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

sort和reverse返回数组的引用

let colors = ["red", "green", "blue"];
let newColors = ["black", "brown"];
let moreNewColors = {
  [Symbol.isConcatSpreadable]: true,
  length: 2,
  0: "pink",
  1: "cyan"
};
newColors[Symbol.isConcatSpreadable] = false;
// 强制不打平数组
let colors2 = colors.concat("yellow", newColors);
// 强制打平类数组对象
let colors3 = colors.concat(moreNewColors);
console.log(colors);   // ["red", "green", "blue"]
console.log(colors2);  // ["red", "green", "blue", "yellow", ["black", "brown"]]
console.log(colors3);  // ["red", "green", "blue", "pink", "cyan"]

所谓打平,就是展开,单个填入,不是整体填入

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

传入负数会加上length,越界自动转化为最近边界
返回切下来的值,原数组不变

splice()

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()
、lastIndexOf() 和includes()

比较是用 === 比较,

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
alert(numbers.indexOf(4));          // 3
alert(numbers.lastIndexOf(4));      // 5
alert(numbers.includes(4));         // true
alert(numbers.indexOf(4, 4));       // 5
alert(numbers.lastIndexOf(4, 4));   // 3
alert(numbers.includes(4, 7));      // false
let person = { name: "Nicholas" };
let people = [{ name: "Nicholas" }];

let morePeople = [person];
alert(people.indexOf(person));      // -1
alert(morePeople.indexOf(person));  // 0
alert(people.includes(person));     // false
alert(morePeople.includes(person)); // true

第二个参数是从第几位开始找,下标

find和findIndex,传入断言,断言函数接收3个参数:元素、索引和数组本身:

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 。

这些方法都不改变调用它们的数组。

同样传入断言

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
alert(everyResult);  // false
let someResult = numbers.some((item, index, array) => item > 2);
alert(someResult);   // true

map会对每个元素进行操作,返回操作后的数组,不改变原数组

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
alert(mapResult);  // 2,4,6,8,10,8,6,4,2

forEach和map类似,但是不返回,改变了原数组

reduce(),reduceRight()

四个参数:

上一个归并值、当前项、当前项的索引和数组本身

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

//只是从右边开始而已
let values = [1, 2, 3, 4, 5];
let sum = values.reduceRight(function(prev, cur, index, array){
  return prev + cur;
});
alert(sum); // 15

上一个归并值就是说return的值

3.定型数组,typed array

发明是为了解决WebGL和JavaScript的数据的传递,因为JavaScript的数据格式与其他语言有些不同,单独转换浪费算力,所以就发明了定型数组

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 某种程度上类似于C++的malloc() ,但也有几个明显的
区别。

malloc() 在分配失败时会返回一个null 指针。ArrayBuffer 在
分配失败时会抛出错误。

malloc() 可以利用虚拟内存,因此最大可分配尺寸只受可寻址系
统内存限制。ArrayBuffer 分配的内存不能超过
Number.MAX_SAFE_INTEGER ( )字节。

malloc() 调用成功不会初始化实际的地址。声明ArrayBuffer 则
会将所有二进制位初始化为0。

通过malloc() 分配的堆内存除非调用free() 或程序退出,否则系
统不能再使用。而通过声明ArrayBuffer 分配的堆内存可以被当
成垃圾回收,不用手动释放。

DataView

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
// 构造函数接收一个可选的字节偏移量和字节长度
//   byteOffset=0表示视图从缓冲起点开始
//   byteLength=8限制视图为前8个字节
const firstHalfDataView = new DataView(buf, 0, 8);
alert(firstHalfDataView.byteOffset);      // 0
alert(firstHalfDataView.byteLength);      // 8
alert(firstHalfDataView.buffer === buf);  // true
// 如果不指定,则DataView会使用剩余的缓冲
//   byteOffset=8表示视图从缓冲的第9个字节开始
//   byteLength未指定,默认为剩余缓冲
const secondHalfDataView = new DataView(buf, 8);
alert(secondHalfDataView.byteOffset);      // 8
alert(secondHalfDataView.byteLength);      // 8
alert(secondHalfDataView.buffer === buf);  // true

相当于DataView就是用来读这块空间的

ElementType用来设置以什么格式去读空间和写空间

// 在内存中分配两个字节并声明一个DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 说明整个缓冲确实所有二进制位都是0
// 检查第一个和第二个字符
alert(view.getInt8(0));  // 0
alert(view.getInt8(1));  // 0
// 检查整个缓冲
alert(view.getInt16(0)); // 0
// 将整个缓冲都设置为1
// 255的二进制表示是11111111(2^8 - 1)
view.setUint8(0, 255);
// DataView会自动将数据转换为特定的ElementType
// 255的十六进制表示是0xFF
view.setUint8(1, 0xFF);
// 现在,缓冲里都是1了
// 如果把它当成二补数的有符号整数,则应该是-1
alert(view.getInt16(0)); // -1

字节序,就是DataView按什么顺序去读空间,有大端字节和小端字节两种顺序。

// 在内存中分配两个字节并声明一个DataView
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
// 填充缓冲,让第一位和最后一位都是1
view.setUint8(0, 0x80); // 设置最左边的位等于1
view.setUint8(1, 0x01); // 设置最右边的位等于1
// 缓冲内容(为方便阅读,人为加了空格)
// 0x8  0x0  0x0  0x1
// 1000 0000 0000 0001
// 按大端字节序读取Uint16
// 0x80是高字节,0x01是低字节
// 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769
alert(view.getUint16(0)); // 32769
// 按小端字节序读取Uint16
// 0x01是高字节,0x80是低字节
// 0x0180 = 2^8 + 2^7 = 256 + 128 = 384
alert(view.getUint16(0, true)); // 384
// 按大端字节序写入Uint16
view.setUint16(0, 0x0004);
// 缓冲内容(为方便阅读,人为加了空格)

// 0x0  0x0  0x0  0x4
// 0000 0000 0000 0100
alert(view.getUint8(0)); // 0
alert(view.getUint8(1)); // 4
// 按小端字节序写入Uint16
view.setUint16(0, 0x0002, true);
// 缓冲内容(为方便阅读,人为加了空格)
// 0x0  0x2  0x0  0x0
// 0000 0010 0000 0000
alert(view.getUint8(0)); // 2
alert(view.getUint8(1)); // 0

边界情形

DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就
会抛出RangeError

const buf = new ArrayBuffer(6);
const view = new DataView(buf);
// 尝试读取部分超出缓冲范围的值
view.getInt32(4);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32(8);
// RangeError
// 尝试读取超出缓冲范围的值
view.getInt32(-1);
// RangeError
// 尝试写入超出缓冲范围的值
view.setInt32(4, 123);
// RangeError

DataView 在写入缓冲里会尽最大努力把一个值转换为适当的类

型,后备为0。如果无法转换,则抛出错误:

const buf = new ArrayBuffer(1);
const view = new DataView(buf);
view.setInt8(0, 1.5);
alert(view.getInt8(0)); // 1
view.setInt8(0, [4]);
alert(view.getInt8(0)); // 4
view.setInt8(0, 'f');
alert(view.getInt8(0)); // 0
view.setInt8(0, Symbol());
// TypeError

定型数组就是和DataView类似的对ArrayBuffer的一种视图,内容很多,但是感觉不常用,以后要用的时候再细致学习。

4.Map 映射

创建映射:

const m = new Map();

使用:

// 使用嵌套数组初始化映射
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,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

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

	const m = new Map().set("key1", "val1");
	m.set("key2", "val2")
	 .set("key3", "val3");
	alert(m.size); // 3

NaN作为键,可以能有以下错误:

const m = new Map();
const a = 0/"", // NaN
      b = 0/"", // NaN

      pz = +0,
      nz = -0;
alert(a === b);   // false
alert(pz === nz); // true
m.set(a, "foo");
m.set(pz, "bar");
alert(m.get(b));  // foo
alert(m.get(nz)); // bar

map里的顺序和set顺序一样,迭代会用这种顺序迭代。

const m = new Map([
  ["key1", "val1"],
  ["key2", "val2"],
  ["key3", "val3"]
]);
alert(m.entries === m[Symbol.iterator]); // true
for (let pair of m.entries()) {
  alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
for (let pair of m[Symbol.iterator]()) {
  alert(pair);

}
// [key1,val1]
// [key2,val2]
// [key3,val3]

键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。
当然,

const m1 = new Map([
  ["key1", "val1"]
]);
// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) {
  key = "newKey";
  alert(key);             // newKey
  alert(m1.get("key1"));  // val1
}
const keyObj = {id: 1};
const m = new Map([
  [keyObj, "val1"]
]);
// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
for (let key of m.keys()) {
  key.id = "newKey";
  alert(key);            // {id: "newKey"}
  alert(m.get(keyObj));  // val1
}
alert(keyObj);           // {id: "newKey"}

选择Object还是Map

内存占用:不同浏览器的情况不同,但给定固定大小的内存,Map 大约可
以比Object 多存储50%的键/值对。

插入性能:不过插入Map
在所有浏览器中一般会稍微快一点儿

如果代码涉及大量插入操
作,那么显然Map 的性能更佳

查找速度:从大型Object 和Map 中查找键/值对的性能差异极
小,但如果只包含少量键/值对,则Object 有时候速度更快。如果代码涉及大量查找操作,那么某些情况
下可能选择Object 更好一些

删除性能:而对大多数浏览器引擎来说,Map 的delete() 操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择Map 。

5.WeakMap,弱映射

创建:

const wm = new WeakMap();

弱映射中的键只能是Object 或者继承自Object 的类型,尝试使用非对
象设置键会抛出TypeError 。值的类型没有限制。

const key1 = {id: 1},
      key2 = {id: 2},
      key3 = {id: 3};
// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([
  [key1, "val1"],
  [key2, "val2"],
  [key3, "val3"]
]);
alert(wm.get(key1)); // val1
alert(wm.get(key2)); // val2
alert(wm.get(key3)); // val3
// 初始化是全有或全无的操作
// 只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2 = new WeakMap([
  [key1, "val1"],
  ["BADKEY", "val2"],

  [key3, "val3"]
]);
// TypeError: Invalid value used as WeakMap key
typeof wm2;
// ReferenceError: wm2 is not defined
// 原始值可以先包装成对象再用作键
const stringKey = new String("key1");
const wm3 = new WeakMap([
  stringKey, "val1"
]);
alert(wm3.get(stringKey)); // "val1"

操作和map差不多,但是所谓弱映射,就是说键是对象,对象没有被引用的时候,这个映射就会被回收

可以用一个代码块来保存这个引用,让映射不被回收

const wm = new WeakMap();
const container = {
  key: {}
};
wm.set(container.key, "val");
function removeReference() {
  container.key = null;
}

弱映射不可迭

弱映射的应用:

DOM节点元数据:

const m = new Map();
const loginButton = document.querySelector('#login');
// 给这个节点关联一些元数据
m.set(loginButton, {disabled: true});

dom节点删除后,映射可以自动被回收

6.Set ,是一种加强Map

创建:

const m = new Set();

// 使用数组初始化集合
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() 删除元素:

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() 返回集合的实例

const s = new Set().add("val1");
s.add("val2")
 .add("val3");
alert(s.size); // 3

相当于Set是没有键的Map,相当于键是元素本身。

7.WeakSet 若集合

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

和弱映射很类似,基本就是说,当传入的对象的引用切断后,会被回收。

和弱映射有相似作用,具体语法和集合很像。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值