[联署材料红皮书说明] 藏品参考类型
序言
承接上一章节的基本引用类型,本章节笔记是关于集合引用类型。集合引用类型包括对象、数组和定型数组、Map、WeakMap、Set、WeakSet。其中的定型数组与后面的 “动画与 Canvas 图形(第18章)”、和“第二十章的 JavaScript API” 中的第一节中才有涉及(我目前看到的),因此建议存到后面看,不影响本章及之后的大部分内容。
MIND
Object
Object 类型最为常见,其实例适合存储和在应用程序间交换数据。详细部分见第八章。
创建
第一种,使用构造函数
let obj = new Object();
obj.name = 'JayeZhu';
第二种,使用对象字面量,更推荐
let obj = { name: 'JayeZhu' };
使用该方式应该注意,数值属性会自动转换为字符串
const obj = { 1: 1 };
console.log(obj['1']); // 1
访问
可通过点读法obj.name
和中括号obj['name']
来读取,区别如下:
- 中括号可用变量方式访问属性
let obj = { name: 'JayeZhu' }; const key = 'name'; console.log(obj[key]); // 'JayeZhu'
- 属性名中存在导致语法错误的字符,或包含关键字/保留字,用中括号
- 属性名包含非字母数字字符,使用中括号
虽然中括号万般好,但点读法是面向对象的惯例,更为推荐
Array
Array 也极为常用。Array 实例是一组有序的数据,数组中槽位可以存储任意类型的数据,并且会随着数据添加而自动增长。
创建
可通过多种方式创建,需要注意的是:
- 构造函数:
单个数值
创建长度为数值的数组,其数组元素为空let arr1 = new Array(); // 空数组 let arr2 = new Array(5); // 单个数值创建 length 为数值的数组 console.log(arr2); // [empty × 5]
- 数组字面量:避免使用空格,ES6 前后的方法处理空格不一致,建议填充 undefined
let arr3 = [1, 2, 3, 4]; console.log([,,,]); // [empty x 3] console.log(Array.of(...[,,,]); // [undefined, undefined, undefined] console.log([,,,].join('-')); // "--"
- Array.from():功能还是很多的
// 字符串拆分 console.log(Array.from('JayeZhu')); // ["J", "a", "y", "e", "Z", "h", "u"] // 集合或映射转换(后文介绍集合和映射) const map = new Map().set(1, 2).set(3, 4); console.log(Array.from(map)); // [[1, 2], [3, 4]; const set = new Set().add(1).add(2); console.log(Array.from(set)); // [1, 2]; // 浅复制 const arr1 = [1, 2, 3, 4]; const arr2 = Array(arr1); console.log(arr1 === arr2); // true // 迭代对象(第七章,迭代器与生成器) const iter = { // 声明迭代对象 *[Symbol.iterator] () { // 生成器函数,产生生成器对象 yield 1; // yield 生成器停止和开始执行 yield 2; } }; console.log(Array.from(iter)); // [1, 2]; // arguments 对象转换(第10章 函数,290页) function getArr () { // arugments 是类数组对象 return Array.from(arugments); } console.log(getArr(1, 2, 3, 4)); // [1, 2, 3, 4]; // 带有必要属性的自定义对象 const arrLikeObj = { 0: 1, 1: 2, length: 2, }; console.log(Array.from(arrLikeObj)); // [1, 2];
- Array.of():笨拙
// 将参数转为数组,替代 Array.prototype.slice.call(arugments) console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4);
访问
使用中括号以及索引访问数组
let list = [1, 2, 3, 4];
// 正常访问
console.log(list[1]); // 2
// 超出范围访问返回 undefined
console.log(list[4]); // undefined
// 增加 length,就用 undefined 填充
list.length = 5;
console.log(list); // [1, 2, 3, 4, undefined]
// 减少 length,删除对应索引元素
list,length = 3;
console.log(list); // [1, 2, 3]
方法
数组方法还是挺多的,成为 API 调用工程师是基本要求,详细建议访问 MDN 查看。
迭代器方法
迭代器知识见本书第七章
- keys():返回数组索引的迭代器
- values():返回数组元素的迭代器
- entries():返回索引/值对的迭代器
let arr = ['a', 'r', 'r']; console.log(arr.keys()); // [0, 1, 2] console.log(arr.values()); // ['a', 'r', 'r'] console.log(arr.keys()); // [[0, 'a'], [1, 'r'], [2, 'r'],]
复制和填充方法
- fill():向已有数组插入全部或部分的值
const arr = [1, 2, 3, 4, 5]; arr.fill(6, 2, 4); // 第一个参数时插入值,第二个参数为插入开始位置,最后一个参数时插入结束位置 console.log(arr); // [1, 2, 6, 6, 5]
- copyWithin():指定范围浅复制数组中的部分内容,然后将其插入到指定索引开始的位置
const arr = [0, 1, 2, 3, 4, 5, 6, 7]; arr.copyWithin(5, 1, 4); // 第一个参数指复制到的位置,第二个参数复制元素起始位置,第三个参数指复制元素起始位置 console.log(arr); // [0, 1, 2, 3, 4, 1, 2, 3]
转换方法
- valueOf():返回数组本身
- toString():数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串
- toLocaleString():调用每个值的
toLocalStorage()
方法,返回逗号分隔的数组值的字符串const arr = [1, 2, 3, 4, 5]; console.log(arr.valueOf()); // [1, 2, 3, 4, 5]; console.log(arr.toString()); // "1,2,3,4,5" arr[0] = { toLocaleString() { return "toLocaleString"; }, toString() { return "toString"; }, }; console.log(arr.toLocaleString()); // "toLocaleString,2,3,4,5"
栈方法
- push():将任意数量的参数添加到数组末尾,返回数组的最新长度
- pop():删除数组的最后一项,数组长度 -1,返回被删除的项
const arr = []; console.log(arr.push(1, 2)); // 2 console.log(arr); // [1, 2] console.log(arr.pop()); // 2 console.log(arr); // [1]
队列方法
- shift():删除数组第一项并返回该项,数组长度 -1
- unshift():数组开头加入任意多的值,返回数组最新长度
const arr = [1]; console.log(arr.unshift(2, 3)); // 3 console.log(arr); // [2, 3, 1] console.log(arr.shift()); // 2 console.log(arr); // [2, 3]
排序方法
- reverse():数组元素反向排列
const arr = [1, 2, 3, 4, 5]; arr.reverse(); console.log(arr); // [5, 4, 3, 2, ,1]
- sort():默认将每一项
String()
转换后升序排列,也可传入比较函数实现排列const arr = [1, 2, 3, 4, 10, 5]; arr.sort(); console.log(arr); // [1, 10, 2, 3, 4, 5] arr.sort((a, b) => a - b)); // 传入比较函数 console.log(arr); // [1, 2, 3, 4, 5, 10]
因此数值数组不适合直接使用 `sort()`,老老实实传比较函数吧
操作方法
- concat():创建当前数组副本,并将参数添加到副本末尾,组成一个新数组
此处传入的const arr1 = [1, 2, 3]; console.log(arr1.concat('4', [5, 6]); // [1, 2, 3, "4", 5, 6]
[5, 6]
打平了,与数组中的 Symbol.isConcatSpreadable 有关,该项默认 true-打平,设置为 false 可防止打平const arr2 = [5, 6]; arr2[Symbol.isConcatSpreadable] = false; console.log(arr1.concat('4', arr2); // [1, 2, 3, "4", [5, 6]]
- slice():创建一个包含原数组中一个或多个的新数组
const arr3 = [1, 2, 3, 4, 5]; console.log(arr3.slice(1, 3)); // [2, 3]
- splice():删除、插入、替换,功能强大
const arr = [1, 2, 3, 4, 5]; // 删除,传入需要删除的第一个元素位置以及删除的元素数量,并返回删除元素组成的数组 console.log(arr.splice(0, 2)); // [1, 2] console.log(arr); // [3, 4, 5]; // 插入,传入开始位置、0(不删除元素)、要插入的元素,返回空数组 console.log(arr.splice(0, 0, 1)); // [] console.log(arr); // [1, 3, 4, 5]; // 替换,传入被替换位置、要删除元素的数量、任意多的需要插入的元素,返回被替换元素组成的数组 console.log(arr.splice(0, 1, 2); // [1] console.log(arr); // [2, 3, 4, 5];
搜索和位置方法
- indexOf():从头搜索,返回第一个符合元素的索引或 -1
- lastIndexOf():跟
indexOf()
分头行动 - includes():存在符合断言函数就返回 true,否则返回 false
- find():返回符合断言函数的第一个元素,否则返回 undefined
- findIndex():返回 find 返回元素的索引或者 -1
const arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]; console.log(indexOf(1)); // 0 console.log(lastIndexOf(1)); // 8 console.log(arr.includes(5); // true console.log(arr.find(x => x > 4); // 5 console.log(arr.findIndex(x => x > 4); // 4
用断言函数搜索数组,接受三个参数:元素、索引和数组本身,返回真值,表示是否匹配
迭代方法
以下方法中接收两个参数:每个元素需要运行的传入函数、可选的作为函数运行上下文的作用域对象(影响函数中 this 的值,函数中 this 的知识点见本书第十章函数,312 页)
- every():传入函数都返回 true,则它返回 true,否则 返回 false
- some():传入函数有返回 true,则它返回 true,否则 返回 false
- filter():返回第一个传入函数返回 true 的元素,没有遇到就返回 undefined
- forEach():执行传入函数,不返回值
- map():执行传入函数,返回每次函数调用结果组成的数组
const arr = [1, 2, 3, 4, 5]; console.log(arr.every(x => x > 0)); // true console.log(arr.some(x => x > 5)); // false console.log(arr.filter(x => x > 3)); // 4 const arr2 = []; arr.forEach(x => arr2.push(2 * x)); console.log(arr2); // [2, 4, 6, 8, 10] console.log(arr.forEach(x => 2 * x)); // [2, 4, 6, 8, 10]
归并方法
以下两个方法接收两个参数:每一项都会运行的递归函数、可选的以之为起点的初始值
- reduce():从前往后迭代所有项,并构建一个最终的返回值
- reduceRight():从后往前。。。
const arr = [1, 2, 3, 4, 5]; console.log(arr.reduce((prev, cur, index, arr) => prev + cur)); // 15 console.log(arr.reduceRight((prev, cur, index, arr) => prev + cur)); // 15
Map
Map 是 ES6 新增的集合类型,但是和 Object 类型一样采用 键/值对 的存储方式,是 Object 不香了吗?其实是以下原因:
- 内存占用:相同内存,Map 相比 Object 多存储 50% 的键/值对
- 插入性能:设计大量插入操作,Map 性能更佳
- 删除性能:Map 的 delete() 操作快且彻底,适合大量删除操作
- 键值*:相比 Object 键只能取自数值、字符串或者符号,它可以使用任何 JS 类型作为键
创建
通过 Map 构造函数创建
const m = new Map([
['key1', 'value1'],
['key2', 'value2'],
]);
此外,可以使用自定义迭代器初始化 map
const m = new Map([
[Symbole.iterator]: function* () { // function 加 * 代表生成器,见第七章 192
yield ['key1', 'value1']; // yield 让生成器停止和开始执行,同第七章
yield ['key2', 'value2'];
}
]);
基本 API
- get():查询传入键对应的值,否则返回 undefined
- has(): 查询是否有传入键,有就返回 true,否则返回 false
- set(): 添加键/值对
- delete():删除传入键对应的键值对
- clear():清除 map 实例所有键/值对
const m = new Map([]); // 添加 m.set('name', 'JayeZhu') .set('job', 'fe') .set('city', 'shen zhen'); console.log(m.size); // 3 // 查询 console.log(m.has('name')); // true console.log(m.get('name')); // "JayeZhu" // 删除 m.delete('name'); console.log(m.size); // 2 m.clear(); console.log(m.size); // 0
顺序和迭代
- entries(): 返回 Map 实例的迭代器,是 Map 实例的默认迭代器
- keys():返回键 Map 实例键的迭代器
- values():返回键 Map 实例值的迭代器
- forEach():迭代键值对
const m = new Map([ ['key1', 'value1'], ['key2', 'value2'], ]); console.log([...m.entries]); // [['key1', 'value1'], ['key2', 'value2']] console.log(m.keys()); // ['key1', 'key2'] console.log(m.values()); // ['value1', 'value2']
WeakMap
WeakMap 与 Map “兄弟”类型,其 API 是 Map 的子集。“weak”描述的是 JS 垃圾回收程序对待 WeakMap 中键的方式
可能比较难理解,特别是对于 “weak” 的解释。“weak” 代表 WeakMap 实例的键不属于正式的引用,属于弱引用,也就是说该实例存在对键引用对象的弱引用。
在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。
const obj = {}; // 强引用
obj = null; // obj 回收
const wm = new WeakMap([{}, 'JayeZhu']); // 弱引用,执行完后等待垃圾回收
我们来看看这个特性让 WeakMap 与 Map 产生了那些不同
创建
与 Map 类似,也使用构造函数创建实例,但 WeakMap 实例中的键属于弱引用,因此只能是 Object 或者继承自 Object 的类型,否则会抛出 TypeError
,并导致整个初始化失败
const key1 = { id: 1 };
const key2 = { id: 2 };
const wm = new WeakMap([
[key1, 'value1'],
['key2', 'value2'],
]); // TypeError: Invalid value used as WeakMap **Key**
Typeof m; // ReferenceError: m is not defined
基本 API
WeakMap API 是 Map API 的子集,其中 clear()
已经废除,原因是其实例的键都随时可能被销毁,那么一次性销毁所有键/值对也用不上了
顺序和迭代
因为 WeakMap 实例的键/值对随时可能销毁,且即使可以访问实例也无法看到内容,迭代也不可能了
使用
私有变量
私有变量是定义在函数或块中的变量,详见第十章函数中的私有变量(316页),利用闭包(引用另一个作用于中变量的函数,同第十章309页)将 WeakMap 包装起来
const User = (() => {
const wm = new WeakMap();
class User { // 类,详见第八章
constructor (id) { // 构造器,详见类250页
this.isProperty = Symbol('id');
this.setId(id);
}
setPrivate (property, value) {
const privateMembers = wm.get(this) || {};
privateMembers[property] = value;
wm.set(this, privateMembers);
}
getPrivate (property) {
return wm.get(this)[property];
}
setId (id) {
this.setPrivate(this.idProperty, id);
}
getId (id) {
return this.getPrivate(this.idProperty);
}
}
return User;
})(); // 立即执行函数,详见函数章节314页
const user = new User(1);
console.log(user.id); // undefined
console.log(user.getId()); // 1
user.setId(2);
console.log(user.getId()); // 2
DOM 节点元数据
DOM之前介绍过,是文档对象模型,JS 的三大实现之一,详见第十四章。因为弱引用的特性:不会妨碍垃圾回收,非常适合保存关联元数据。
const wm = new WeakMap();
const loginButton = document.querySelector('#login'); // 获取 id 为 login 的button元素,详见第16章Dom扩展 445页
wm.set(loginButton, { disabled: true });
// 关联元数据,当 loginButton 引用的元数据从 DOM 树中消失,WeakMap 中的 loginButton 不再被强引用,
// 变为弱引用,而后被垃圾回收,而不会出现 DOM 节点仍然逗留在内存中的情况。
Set
Set 是加强的 Map,带来了集合数据结构
创建
同样也是构造函数创建实例,但初始化时传入一个可迭代对象,其中需要包含插入到心机和实例中的元素
const s = new Set(['value1', 'value2']);
此外,同样可以使用自动以迭代器
const s = new Set({
[Symbole.iterator]: function* () {
yield ['key1', 'value1'];
yield ['key2', 'value2'];
}
[
基本API
与 Map 很相似,通过 has() 查询,delete() 和 clear() 删除,不同的是,Set 实例通过 add() 增加值,并且没有 get() 方法
const s = new Set();
s.add(1);
console.log(s.size); // 1
顺序和迭代
与 Map 相比,除了 forEach() 意向,其他三个差异很大,其中 values() 是 Set 实例的默认迭代器,keys() 是 values() 的别名,entries() 返回迭代器,其中的元素是集合中每个值的重复出现
const s = new Set([1, 2, 3]);
console.log([...s.values()]); // [1, 2, 3];
console.log([...s.keys()]); // [1, 2, 3];
console.log([...s.entries]); // [[1, 1], [2, 2], [3, 3]];
WeakSet
理解了上面的 WeakMap,WeakSet 也就不难理解了,基本和 WeakMap 一样,但 WeakSet 用处没有那么大,可用于给对象打标签
const disabledElements = new WeakSet();
const loginbutton= docuumment.querySelectrot('#login');
disabledElements.add(loginButton);
// 给节点打上禁用标签,WeakSet 中元素从 DOM 树中删除
// 垃圾回收组程序就出来干活了
总结
本章的 Object 会在后面章节大放异彩,主要关心 Array 以及 ES6 新增的 Map、WeakMap、Set、WeakSet,需要好好理解掌握,诚然,它们的 API 确实很多,基本离不开文中常见的字眼“迭代器”抑或“生成器”,下章就会细细讲述关于迭代器以及生成器的东西。
往期笔记
凌晨肝完了,文中可能有误,欢迎提出。
- 1-什么是JS
- 2-HTML中的JavaScript
- 3-语言基础
- 4-变量、作用域与内存
- 5-基本引用类型
上一篇: 加工中心工艺分析示例
下一篇: 工业机器人切割、铣削、钻孔自动化加工应用
推荐阅读