欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

[联署材料红皮书说明] 藏品参考类型

最编程 2024-03-11 10:16:46
...

序言

承接上一章节的基本引用类型,本章节笔记是关于集合引用类型。集合引用类型包括对象、数组和定型数组、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-基本引用类型