JavaScript高级程序设计(第4版)-第6章 集合引用类型

第6章 集合引用类型

6.1 Object

JavaScript中的Object实例没有多少功能,但是很适合存储和在应用程序间交换数据

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

  • new操作符和Object构造函数
let person = new Object();
person.name = 'SnowField'
person.age = 24
  • 对象字面量(object literal)表示法
  • let person = {
        name : 'SnowField',
        age : 24
    }

     

    • 对象字面量目的:简化包含大量属性的对象的创建
    • 对象字面量出现在表达式上下文(expression context)中
      • 表达式上下文:期待返回值的上下文
    • 对象字面量中属性定义语法:
      • 使用逗号在对象字面量中分隔属性
      • 属性名可以是字符串或数值
      • 数值属性会自动转换为字符串
    • 对象字面量表示法并不会实际调用Object构造函数
    • 推荐对必选参数使用命名参数,对可选参数使用对象字面量来封装

可以使用typeof操作符来测试每个属性是否存在

function displayInfo(args){
    let output = "";
    if ( typeof args.name == 'string' ) {
        output += "Name: " + args.name + '\n'
    }
    if ( typeof args.age == 'number' ) {
        output += 'Age: ' + args.age + '\n'
    }
    console.log(output)
}
displayInfo({
    name : 'SnowField',
    age : 24
})
displayInfo({
    name : 'SnowField'
})

属性可以通过两种方式来进行存取:

  • 点语法:首选
  • 中括号:支持通过变量访问属性,变量可以包含非字母数字字符
let person = {
    'first name' : 'Linton'
}
console.log(person['first name']) //Linton

6.2 Array

ECMAScript数组具有如下特性:

  • 数组中每个槽位可以存储任意类型的数据
  • 动态大小

6.2.1 创建数组

可以使用如下几种方式来创建数组:

  • Array构造函数
  • let arr = new Array()

     

    • new Array(Number):创建一个length为Number的Array
    • let arr = new Array(10)
      console.log(arr) //[empty * 10]
    • new Array(param1[, param2, ...]):创建一个包含传入值的Array
    • let arr1 = new Array('param1','param2','param3')
      let arr2 = new Array(1,2,3)
      console.log(arr1) // ['param1', 'param2', 'param3']
      console.log(arr2) //[ 1, 2, 3]
  • 数组字面量(array literal)
  • let arr = [ 1, 2, 'hello', { name:'SnowField' }]

     

    • 不会调用Array构造函数
  • ES6新增的创建数组的静态方法
    • Array.from(iterable [, mapFunction [, functionThis]])
      • 功能:用于将类数组结构转换为数组实例
        • 将字符串拆分为单字符数组
          console.log(Array.from("Hello")) //['H', 'e', 'l', 'l', 'o']
        • 将集合和映射转换为一个新数组
          const m = new Map().set(1, 2).set(3, 4) //{1=>2, 3=>4}
          const s = new Set().add(1).add(2).add(3).add(4) //{1,2,3,4}
          console.log(Array.from(m)) //[ [1,2], [3,4]]
          console.log(Array.from(s)) //[1,2,3,4]
        • 对现有数组执行浅复制
          const a1 = [1 ,2 ,3 ,4]
          const a2 = Array.from(a1)
          console.log(a1) //[1,2,3,4]
          console.log(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]
        • 转换带有必要属性的自定义对象
          const arrayLikeObject = {
              0:1,
              1:2,
              2:3,
              3:4,
              length:4,
          }
          console.log(Array.from(arrayLikeObject)) //[1,2,3,4]
      •  类数组需要满足下面任一条件
        • 具有可迭代的结构
        • 有一个length属性
        • 具有可索引元素的结构
      • 参数:
        • iterable:一个类数组对象
        • mapFunction:映射函数,可以用于增强新数组的值
        • functionThis:指定欧映射函数中this的值,该值在箭头函数中不起作用
          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]
    • Array.of(param)
      • 功能:用于将一组参数转化为数组实例
        console.log(Array.of(1,2,3,4)) //[1,2,3,4]
        console.log(undefined) // [undefined]

6.2.2 数组空位

数组空位(hole)有如下特性

  • 可以使用连续逗号创建
    • const options = [,,,,,]
      console.log(options.length) //5
      console.log(options) // [ empty * 5 ]
    • ES6的方法和迭代器普遍将空位元素当成undefined
      const options = [1,,,,5];
      for(const option of options){
          console.log(option === undefined); //false true true true false
      }
      const a = Array.from([,,,]);
      for(const val of a){
          console.log(val === undefined); // true true true
      }
      console.log(Array.of(...[,,,])) // [ undefined, undefined, undefined]
    •  ES6之前的方法会对空位进行忽略或特别处理
      const option = [1,,,,5];
      console.log(option.map(item=>6)) //[6, empty*3, 6];
      console.log(option.join('-')) //1----5
  •  不推荐使用数组空位:
    • 原因:行为不一致,且存在性能隐患
    • 推荐:显式使用undefined值代替

6.2.3 数组索引

数组的length始终等于max(index)+1

数组length的独特之处在于它不只是只读的,可以通过修改length属性来删除或添加数组尾部元素。

const option = Array.from("hello");
console.log(option.length); //5
option.length = 10;
console.log(option) //['h','e','l','l','o',empty*5]
option.length = 3;
console.log(option); //['h','e','l']

数组可以通过索引来指定超过Array.length位置的元素,中间位置的元素使用undefined进行占位

const option = Array.from("hello");
option[100] = "world";
console.log(option); // ['h','e','l','l','o',empty*95,'world']

数组最大长度:4294967295,超过这个长度会导致RangeError

console.log(new Array(4294967295+1)) // RangeError:Invalid array length

6.2.4 检测数组

一般使用两种方式来检测数组

  • instanceof
    • 注意:instanceof本身是用来检测构造函数原型的,因此在使用时需要注意必须要在数组所属的上下文中使用。
    • instanceof方法不适用于跨框架传递的数组,这是因为不同框架的原始类型构造函数属于不同的全局对象
  • Array.isArray()
    • 为了解决instanceof跨框架无法检测的问题而提出的解决方案
const option = Array.from('hello')
console.log(option instanceof Array);  // true
console.log([] instanceof Array)    // true
console.log(null instanceof Array)  //false
console.log(Array.isArray(option)) // true

6.2.5 迭代器方法

ES6中,Array的原型上暴露了3个用于检索数组内容的方法:

  • keys() 返回数组索引的迭代器
  • values() 返回数组元素的迭代器
  • entries() 返回索引/值对的迭代器
const arr = Array.from("hello")
console.log(Array.from(arr.keys())) //[ 0, 1, 2, 3, 4]
console.log(Array.from(arr.values())) //['h','e','l','l','o']
console.log(Array.from(arr.entries())) //[[0,'h'],[1,'e'],[2,'l'],[3,'l'],[4,'o']]
for(const [index,element] of arr.entries()){
    console.log(index+":"+element) //0:h 1:e 2:l 3:l 4:o
}

6.2.6 复制和填充方法

  • 批量复制方法 copyWithin() 
    • 功能:按照指定范围浅复制数组中的部分内容
  • 填充数组方法 fill( value[, start[, end]])
    • 功能:向一个已有数组中插入全部或部分相同的值
    • 参数:
      • value:填充值
      • start:开始填充索引,不传默认为0,即数组首位
      • end:填充结束索引,不传默认为Array.length-1,即数组末尾
    • 几种无效情况:
      • 索引范围低于有效范围
      • 索引范围高于有效范围
      • 索引反向
      • 索引部分可用,则忽略不可用部分
const zeroes = [ 0, 0, 0, 0, 0]
zeroes.fill(5);
console.log(zeroes); // [ 5, 5, 5, 5, 5]
zeroes.fill(0); // [ 0, 0, 0, 0, 0]
zeroes.fill(6, 3); // [ 0, 0, 0, 6, 6]
console.log(zeroes);
zeroes.fill(0);
zeroes.fill(7,1,3);
console.log(zeroes) // [ 0, 7, 7, 0, 0]
zeroes.fill(0)
zeroes.fill(8, -4, -1)
console.log(zeroes) //[ 0, 8, 8, 8, 0]

6.2.7 转换方法

let colors = ["red",'blue','green'];
console.log(colors.valueOf()) //['red','blue','green']
console.log(colors.toString()) //red,blue,green
console.log(colors.toLocaleString()) //red,blue,green
  • toLocaleString
    • 返回值:数组中的每个值的等效字符串由逗号拼接成的字符串
    • 原理:将其每一个元素进行toLocaleString转换并由逗号拼接
  • toString
    • 返回值:数组中每个值的等效字符串拼接而成的由逗号分隔的字符串
    • 原理:将其每一个元素进行toString转换并由逗号拼接
let person1 = {
    toLocaleString(){
        return 'Linton'
    },
    toString(){
        return 'SnowField'
    }
}
let person2 = {
    toLocaleString(){
        return 'Owen'
    },
    toString(){
      return 'Woody'
    }
}
let person = [person1,person2];
console.log(person.toString()); //SnowField,Woody
console.log(person.toLocaleString()); //Linton,Owen
  • valueOf
    • 返回值:数组本身
  • join
    • 参数:字符串分隔符
    • 原理:调用数组每一个元素的toString方法,并使用分隔符参数将其连接成字符串
    • 空值:对于null或undefined,转换方法会将其转换为空字符串
let colors = ["red",null,'blue',undefined,'green']
console.log(colors.join(",")); //red,,blue,,green
console.log(colors.join('||')) //red||||blue||||green

6.2.8 栈方法

  • push
    • 功能:将数据项推入数组尾部,Array.length+=1
    • 返回值:Number,推入后的数组长度
  • pop
    • 功能:将数组的最后一项弹出,Array.length-=1
    • 返回值:弹出项
let colors = new Array();
let count = colors.push('red','green');
console.log(count) // 2
count = colors.push('black')
console.log(count) // 3
let item = colors.pop();
console.log(item); // black
console.log(colors.length); // 2

6.2.9 队列方法

使用push和shift方法的组合可以将数组当做队列来使用

使用pop和unshift方法的组合可以将数组当做反方向队列来使用

  • shift
    • 功能:删除数组第一项,Array.length-=1
    • 返回值:数组第一项
let colors = new Array();
let count = colors.push('red','green');
count = colors.push('black');
console.log(count); // 3
let item = colors.shift();
console.log(item); // red
console.log(colors.length); //2
  • unshift
    • 功能:在数组首索引位置添加数据,Array.length+=1
    • 返回值:添加数据后数组的长度

6.2.10 排序方法

  • reverse
    • 功能:将数组元素进行反向排序
  • sort
    • 功能:默认情况下按照升序重新排列数组元素
    • 参数:比较函数,判定比较规则
    • 比较函数规则:
      • 传参:
        • param1:临近比较元素中索引低的元素
        • param2: 临近比较元素中索引高得到元素
      • 返回值:
        • 负值/0:临近元素不交换位置
        • 正值:临近元素交换位置
let values = [1,2,5,15,20];
values.sort();
console.log(values); // [ 1, 15, 2, 20, 5]
let values = [ 1, 15,2,20,5];
values.sort((value1,value2)=>{
    if(value1>value2){
        return 1;
    }else if(value1 === value2){
        return 0;
    }else{
        return -1
    }
})
console.log(values); //[1,2,5,15,20]

6.2.11 操作方法

  • concat
    • 功能:在现有数组全部元素基础上创建一个新数组
    • 原理:首先创建一个当前数组的副本,然后把参数添加到副本末尾,最后返回这个新构建的数组
    • 可重写性:打平数组参数的行为能够重写,通过对参数数组指定特殊符号Symbol.isConcatSpreadable
      • Symbol.isConcatSpreadable = true 表示该参数需要打平
      • Symbol.isConcatSpreadable = false 表示该参数无需打平
let originArr = ["red", 'green', 'blue'];
let spreadableArr = ['black', 'brown']
let spreadisableArr = ['pink', 'cyan']
spreadableArr[Symbol.isConcatSpreadable] = true;
spreadisableArr[Symbol.isConcatSpreadable] = false;
// ['red','green','blue','black','brown',['pink','cyan']]
console.log(originArr.concat(spreadableArr, spreadisableArr));
  • slice
    • 功能:创建一个包含原有数组中一个或多个元素的新数组,不影响原始数组
    • 参数:
      • 一个参数:表示单个索引,返回该索引到数组末尾的所有元素 
      • 两个参数:表示开始索引与结束索引
      • 负值参数:换算为数值长度+负值参数确定位置
let originArr = ["red", "green", 'blue', 'yellow', 'purple'];
let newArr1 = originArr.slice(1);
let newArr2 = originArr.slice(1, 4);
console.log(newArr1); //['green','blue','yellow','purple']
console.log(newArr2) //['green','blue','yellow']
  • splice
    • 功能:
      • 删除
        • 参数1:删除的第一个元素的位置
        • 参数2:要删除的元素数量
      • 插入
        • 参数1:开始插入的位置
        • 参数2:0,表示不进行删除
        • 参数[ 3, ...]:要插入的单(多)个元素
      • 替换
        • 参数1:开始位置
        • 参数2:要删除的元素数量
        • 参数3:要插入的单(多)个元素
    • 返回值:数组,包含从数组中被删除的元素
let originArr = ['red', 'green', 'blue']
let remove = originArr.splice(0, 1); 
console.log(originArr,remove); //['green','blue'] ['red']
remove = originArr.splice(1, 0, 'yellow', 'orange')
console.log(originArr,remove); //['green','yellow','orange','blue'] []
remove = originArr.splice(1, 1, 'red', 'purple')
console.log(originArr,remove) //['green','yellow','purple','orange','blue'] ['yellow']

6.2.12 搜索和位置方法

6.2.12.1 严格相等

严格相等即比较时会使用全等(===)标准来进行判断

严格相等模式下的方法都接收两个参数:

  • 参数1:要查找的元素
  • 参数2:可选的起始搜索位置

严格相等模式包括下面3个方法

  • indexOf:从数组起始位置查找元素在数组中的位置,返回数字索引,没找到时返回-1
  • lastIndexOf:从数组末尾开始查找元素在数组中的位置,返回数字索引,没找到时返回-1
  • includes:从数组首位开始查找元素在数组中的位置,返回布尔值true/false
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
console.log(numbers.indexOf(4)); //3
console.log(numbers.lastIndexOf(4)); //5
console.log(numbers.includes(4)); //true
console.log(numbers.indexOf(4, 4)) //5
console.log(numbers.lastIndexOf(4, 4));// 3
console.log(numbers.includes(4, 7)); // false

let person = { name: 'Linton' };
let people = [{ name: 'Linton' }];
let morePeople = [person];

console.log(people.indexOf(person)); //-1
console.log(morePeople.indexOf(person)) //0
console.log(people.includes(person)); //false
console.log(morePeople.includes(person)); //true

6.2.12.2 断言函数

直接调用数组索引会触发定义的断言函数,

断言函数接受3个参数:

  • 元素:数组中当前搜索的元素
  • 索引:当前元素的索引
  • 数组本身:正在搜索的数组

返回值:boolean类型,表示是否匹配

  • 常用断言函数
    • find
    • findIndex
  • 共同点:
    • 从数组的最小索引开始
    • 都返回第一个匹配的元素的相关信息
    • 都接受第二个可选参数,用于定义函数内部this的值
    • 找到匹配项后就不再继续搜索
const people = [
  {
    name: 'Ben',
    age:27
  },
  {
    name: "Matt",
    age:29,
  }
]
console.log(people.find((element, index, array) => element.age < 28)); //{name:'Ben',age:27}
console.log(people.findIndex((element, index, array)=> element.age < 28)); //0

const events = [2, 4, 6];
events.find((element, index, array) => {
  //2 0 [2,4,6]
  //4 1 [2,4,6]
  console.log(element, index, array);
  return element === 4;
})

6.2.13 迭代方法

ECMAScript所定义的5个迭代方法都具有两个参数:

  • 以每一项为参数运行的函数
    • 参数1:数组元素
    • 参数2:数组索引
    • 参数3:数组本身
  • 可选的作为函数运行上下文的作用域对象
    • 功能:影响函数中this的值

这5个迭代方法分别是:

  • 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);
console.log(everyResult); //false
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult); //true
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); //[3,4,5,4,3]
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult); //[2, 4, 6, 8, 10, 8, 6, 4, 2]

6.2.14 归并方法

  • ECMAScript定义的两种归并方法:
    • reduce
      • 从最小索引开始归并
    • reduceRight
      • 从最大索引开始归并
  • 共同点:
    • 都接受两个参数
      • 归并函数:对每一项都运行
        • 接受参数:
          • 上一个归并值
          • 当前项
          • 当前项的索引
          • 数组本身
        • 返回值:
          • 作为下一次调用同一个函数的第一个参数 
      • 初始值:可选之为归并起点,若未传入则将归并将从数组的第二项开始,初始值则取数组的第一项
let values = [1, 2, 3, 4, 5];
let sum = values.reduce(function (prev, cur, index, array) { 
  return prev+cur
})
console.log(sum) //15
let sum_right = values.reduceRight(function (prev, cur, index, array) { 
  return prev + cur;
})
console.log(sum_right); // 15

6.3 定型数组(typed array)

目的:提升向原生库传输数据的效率

定义:一种包含特殊数值类型的数组

6.3.1 历史

定型数组的需要源自浏览器提供商对一套用于渲染复杂图形应用的JavaScript API的开发目标。

6.3.1.1 WebGL(Web Graphics Library)

作为JavaScript的一个设计复杂图形编程的API,基于OpenGL ES(Embedded Systems)2.0规范的WebGL于2011年发布1.0版。

但是早期的WebGL存在一个问题:JavaScript数组在内存中使用双精度浮点格式进行存储,而图形驱动API不需要双精度浮点格式,因此JS与API若要传输数组则需要进行以下操作:

  1. 在目标环境分配新数组
  2. 以其当前格式迭代数组
  3. 将数值转型为新数组中的适当格式

6.3.1.2 定型数组

Mozilla为了解决上述传输数组数据时效率过低的问题,首个实现了CanvasFloatArray,这种类型的数组可以直接和底层图形驱动程序API交换数据,最终变成了Float32Array

6.3.2 ArrayBuffer

  • 定义:所有定型数组及视图引用的基本单位
  • 描述:定型数组的具体类型实际上是一种“视图”,实际的数据类型规划是由一块名为ArrayBuffer的预分配内存实现的。
  • 变体:SharedArrayBuffer,可以无须复制就在执行上下文间传递
  • ArrayBuffer()
    • 本质:一个普通的JavaScript构造函数
    • 作用:在内存中分配特定数量的字节空间
  • 特点:
    • 一经创建就不能调整大小,可以使用slice进行复制或分配到新的ArrayBuffer实例中
    • 在分配失败时会抛出错误
    • 可分配内存不能超过Number.MAX_SAFE_INTERGER(2^53-1)字节
    • 被ArrayBuffer调用的地址中的实际内存会被初始化为二进制全为0的形式
    • 被ArrayBuffer分配的堆内存可以被当做垃圾回收,无需手动释放
    • 要对ArraayBuffer的堆内存进行读写必须通过视图
const buf = new ArrayBuffer(16);
console.log(buf.byteLength); //16
const buf_copy = buf.slice(4, 12);
console.log(buf_copy.byteLength); //8

6.3.3 DataView

  • 适用域:文件I/O和网络I/O
  • 作用:支持对缓冲数据的高度控制
  • 缺陷:性能较差,无法对缓冲内容进行初始化与迭代
  • 构造函数:
    • 必须在已有的ArrayBuffer基础上创建DataView实例
      • 语法:new DataView(ArrayBuffer)
      • const buf = new ArrayBuffer(16);
        const fullDataView = new DataView(buf);
        /**
         * fullDataView:{
         *    buffer:ArrayBuffer(16),
         *    byteOffset:0,
         *    byteLength:16
         * }
         */
        console.log(fullDataView);
    • 构造函数可以接收一个可选的字节偏移量(byteOffset)和字节长度(byteLength)
      • 语法:new DataView( ArrayBuffer, byteOffset, byteLength)
      • const buf = new ArrayBuffer(16);
        const lastHalfDataView = new DataView(buf, 8, 8);
        /**
         * lastHalfDataView:{
         *    buffer:ArrayBuffer(16),
         *    byteLength:8,
         *    byteOffset:8
         * }
         */
        console.log(lastHalfDataView)
    • 未指定byteOffset,不指定byteLength,则默认占用剩余缓冲(ArrayBuffer.length-byteOffset+1)
      • 语法:new DataView( ArrayBuffer, byteOffset)
      • const buf = new ArrayBuffer(16);
        const endPartDataView = new DataView(buf, 10);
        /**
         * endPartDataView:{
         *    buffer:ArrayBuffer(16),
         *    byteOffset:10,
         *    byteLength:6,
         * }
         */
        console.log(endPartDataView)
  • DataView读取时需要用到的辅助组件
    • 字节偏移量:DataView读写所依赖的地址实质上为读或者写单元的字节偏移量
    • ElementType:用于实现JavaScript中Number与缓冲内二进制格式的相互编译转换
    • 字节序:内存中值的字节序,默认大端字节序
      • 大端字节序(big-endian):最高位字节存储在最低的内存地址处
      • 小段字节序(little-endian):最低位字节存储在最低的内存地址处

6.3.3.1 EelementType

DataView没有指定默认的数据类型,即使用时必须要指定一个ElementType。

ECMAScript6支持8种ElementType

DataView针对每种ElementType都有相对应的get和set方法。这些方法需要通过读取byteOffset来定位需要进行读写操作的地址。

类型是能够互换使用的。

const buf = new ArrayBuffer(2);
const view = new DataView(buf);
console.log(view.getInt8(0)); //0
console.log(view.getInt8(1)); //0
console.log(view.getInt16(0)); //0
view.setUint8(0, 255); //11111111b 
view.setUint8(1, 0xAA); //10101010b 170
/**
 * 有符号数:
 *  原码:1111_1111_1010_1010
 *  反码:1000_0000_0101_0101
 *  补码:1000_0000_0101_0110   -1*(64+16+4+2)=-86
 * 无符号数:
 *  原码:15*16^3+15*16^2+10*16+10 = 61440+3840+160+10=65450
 */
console.log(view.getInt16(0)); //-86
console.log(view.getUint16(0)); //65450  
console.log(view.getInt8(0)) //-1
console.log(view.getInt8(1)) //-86 1_1010_0100
console.log(view.getUint8(0)) //255

6.3.3.2 字节序

  • 定义:计算机系统维护的一种字节顺序的约定
  • 分类:
    • 大端字节序(网络字节序):高位字节存放在低位地址
    • 小端字节序:低位字节存放在低位地址
  • 特性:
    • 不由JavaScript运行时所在的系统的原生字节序决定
    • 中立接口,遵循指定字节序
    • 所有API方法默认为大端字节序
    • 所有API方法通过一个boolean参数
const buf = new ArrayBuffer(2);
const view = new DataView(buf);
view.setUint8(0, 0x80); //1000_0000
view.setUint8(1, 0x01); //0000_0001
/**
 * 大端写入0x8001
 * 高地址———————低地址
 * 低字节———————高字节
 * 1000_0000_0000_0001
 * 大端读取:1000_0000_0000_0001 32769
 * 小端读取:0000_0001_1000_0000 384
 */
console.log(view.getUint16(0)) //32769
//小端读取
console.log(view.getUint16(0, true)) //384

/**
 * 小端写入0002
 * 高地址——————低地址
 * 高字节——————低字节
 * 0000_0010_0000_0000
 * 大端读取:0000_0010_0000_0000 2
 * 小端读取:0000_0000_0000_0010 512
 */
view.setUint16(0, 0x0002, true)
console.log(view.getUint8(0)); //2
console.log(view.getUint8(1)); //0
console.log(view.getUint16(0)); //512
console.log(view.getUint16(0,true)) //2 

6.3.3.3 边界情形

如果没有充足的缓冲区,DataView在读写操作时会抛出RangeError

const buf = new ArrayBuffer(6); //6*8 48
const view = new DataView(buf);
//读取内容部分超出缓冲区 RangeError
view.getInt32(4); //4*8-8*8
//读取内容完全超出缓冲区 RangeError
view.getInt32(8) //8*8-12*8
view.getInt32(-1)
//写入内容超出缓冲区 RangeError
view.setInt32(4, 123);

DataView会对写入缓冲区的数据进行类型转换,无法转换时会报错

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

6.3.4 定型数组

比较定型数组与DataView:

  • 共同点:概念角度上两者都是ArrayBuffer的视图
  • 不同点:
    • 数据类型限制 
      • 定型数组特定于一种ElementType
      • DataView没有特定的数据类型限制
    • 字节序
      • 定型数组的存储遵循系统原生的字节序
      • DataView的存储默认为大端字节序
    • 功能与性能
      • 定型数组提供适用面更广的API
      • 定型数组与WebGL等原生库交换二进制数据的效率更高
      • 定型数组的格式对于操作系统而言更容易操作
      • JavaScript引擎对操作定型数组的操作算法进行了优化

创建定型数组的几种方式:

  • 读取已有的缓冲
    • 构造函数语法:new <ElementType>( buffer : ArrayBuffer )
    • const buf = new ArrayBuffer(12); //创建一个12字节的缓冲
      const ints = new Int32Array(buf); //创建一个引用该缓冲的Int32Array
      console.log(ints.length); //12/(32/8)=3
  • 使用自有缓冲
    • 构造函数语法:new <ElementType>( buffer:Number )
    • const ints2 = new Int32Array(6); //创建一个长度为6的Int32Array
      console.log(ints2.length); //6
      //<ElementType>().buffer 即定型数组指向关联缓冲的引用
      console.log(ints2.buffer.byteLength); //6*(32/8)=24

       

  • 填充可迭代结构
    • 构造函数语法:new <ElementType>( buffer:Iterable Object )
    • //创建一个包含[2,4,6,8]的32Int数组
      const ints3 = new Int32Array([2, 4, 6, 8]);
      console.log(ints3.length); //4
      console.log(ints.buffer.byteLength); //4*(32/8)=16
      console.log(ints3[2]); //6
  • 填充基于任意类型的定型数组
    • 构造函数语法:new <ElementType>( buffer:ElementType )
    • //通过复制Ints3创建一个Int16Array类型的数组
      const ints3 = new Int32Array([2, 4, 6, 8]);
      const ints4 = Int16Array(ints3);
      console.log(ints4.length); //4
      console.log(ints4.buffer.byteLength); //4*(16/8)=8
      console.log(ints4[2]); //6
  • <ElementType>.from()
    • 语法:<ElementType>.from(buffer:Array)
    • const ints5 = Int16Array.from([3, 5, 7, 9]);
      console.log(ints5.length); //4
      console.log(ints5.buffer.byteLength); //4*(16/8)=8
      console.log(ints5[2]); //7
  • <ElementType>.of()
    • 语法:<ElementType>.of(param1,param2,...)
    • const floats = Float32Array.of(3.14, 2.718, 1.618);
      console.log(floats.length); //3
      console.log(floats.buffer.byteLength); //3*(32/8)=12
      console.log(floats[2]); //1.6180000305175781

BYTES_PER_ELEMENT属性:

  • 功能:返回该ElementType中每个元素的大小
  • 可读方式:每种ElementType的构造函数和实例都包含该属性
  • console.log(Int16Array.BYTES_PER_ELEMENT) //2
    console.log(Int32Array.BYTES_PER_ELEMENT) //4
    const ints = new Int32Array(1),
          float = new Float64Array(1);
    console.log(ints.BYTES_PER_ELEMENT); //4
    console.log(float.BYTES_PER_ELEMENT);//8

定型数组未被初始化的缓冲空间会使用0填充

const ints = new Int32Array(4);
console.log(ints[0]); //0
console.log(ints[1]); //0
console.log(ints[2]); //0
console.log(ints[3]); //0

6.3.4.1 定型数组行为

定型数组的操作符、方法、属性具有以下几点特征:

  • 定型数组和普通数组基本具有相似的行为(不完全相等)
  • 定型数组方法(如果有返回数组值)返回数组数据类型不变
const ints = new Int16Array([1, 2, 3]);
const doubleInts = ints.map(x => 2 * x);
console.log(doubleInts instanceof Int16Array); //true
  • 定型数组具有Symbol.iterator符号属性
const ints = new Int16Array([1, 2, 3]);
for (const int of ints) { 
  console.log(int); //1 2 3
}
console.log(Math.max(...ints)); //3

6.3.4.2 合并、复制和修改定型数组

由于定型数组使用ArrayBuffer(ArrayBuffer一经初始化就无法修改大小)存储数据,因此无法使用会修改数组大小的方法(concat,push,pop,shift,unshift,splice等)

定型数组具有2个特有的方法:

  • set( copySource[ ,index])
    • 功能:将参数数组的值复制到调用实例数组的指定索引位置
    • 参数:
      • copySource:Array | typedArray
      • index:默认为0
    • const container = new Int16Array(8);
      container.set(Int8Array.of(1, 2, 3, 4)); //[1,2,3,4]
      console.log(container);  //[1,2,3,4,0,0,0,0]
      container.set([5, 6, 7, 8], 4);
      console.log(container); //[1,2,3,4,5,6,7,8]
      container.set([9, 10], 7); //RangeError
  • subarray( [ startIndex [ , endIndex]] )
    • 功能:将调用方法的实例定型数组的指定位置元素复制到新数组中
    • 参数:
      • 无参:复制全部
      • 1个参数:复制的startIndex
      • 2个参数:复制的startIndex和endIndex
    • return:复制结果生成的新数组
    • const source = Int16Array.of(2, 4, 6, 8); //[2,4,6,8]
      const fullCopy = source.subarray();
      console.log(fullCopy); //[2,4,6,8]
      const halfCopy = source.subarray(2);
      console.log(halfCopy); //[6,8]
      const partialCopy = source.subarray(1, 3);
      console.log(partialCopy); //[4,6]
  • 定型数组的拼接:
    • /**
       * 手动构建定型数组拼接函数
       * @param {*} typedArrayConstructor 拼接后返回定型数组的构造函数 
       * @param  {...any} typedArrays  需要被拼接的定型数组
       * @returns 拼接结果
       */
      function typedArrayConcat(typedArrayConstructor, ...typedArrays) {
        //计算需要的数组空间
        const numElements = typedArrays.reduce((x, y) =>
          (x.length || x) + y.length
        );
        //使用计算后的尺寸计算类型数组实例
        const resultArray = new typedArrayConstructor(numElements);
        //写入的偏移地址
        let currentOffset = 0;
        //逐个写入
        typedArrays.map(x => {
          resultArray.set(x, currentOffset);
          currentOffset += x.length;
        });
        return resultArray;
      }
      
      const concatArray = typedArrayConcat(
        Int32Array, //需要拼接成Int32Array类型的数组
        Int8Array.of(1, 2, 3),  //[ 1, 2, 3]
        Int16Array.of(4, 5, 6),  //[ 4, 5, 6]
        Float32Array.of(7, 8, 9)  //[ 7, 8, 9]
      );
      console.log(concatArray); // [1,2,3,4,5,6,7,8,9]
      console.log(concatArray instanceof Int32Array); //true

6.3.4.3 下溢和上溢

定型数组的上溢和下溢不会影响到其他位

  • unsigned 无符号数
    • 上溢
      • 有效位:从最低有效位开始算起
      • 溢出位:抛弃上溢位,不会影响相邻索引
      • // 长度为2的无符号整数数组 0-255
        const unsignedInts = new Uint8Array(2);
        unsignedInts[1] = 256;  //1 [0000-0000]
        console.log(unsignedInts); //[ 0, 0]
        unsignedInts[1] = 511; //01 [1111-1111]
        console.log(unsignedInts); //[ 0, 255]
    • 下溢
      • 处理方式:会被转换为无符号等价值(二补数形式)
      • const unsignedInts = new Uint8Array(2);
        /**
         * -1
         * 1000-0001
         * 1111-1110
         * 1111-1111 
         * 255
         */
        unsignedInts[1] = -1;
        console.log(unsignedInts); //[ 0, 255]
  • signed 有符号数
    • 上溢
      • 处理方式:自动变成二补数形式
      • // 长度为2的二补数形式有符号整数数组 -128 - 127
        const ints = new Int8Array(2);
        /**
         *  128
         *  0 1000-0000
         *  0 0111-1111
         *  1 1000-0000
         *  -128
         */
        ints[1] = 128; 
        console.log(ints); //[ 0, -128]
    • 下溢
      • 处理方式:自动变成二补数形式
      • // 长度为2的二补数形式有符号整数数组 -128 - 127
        const ints = new Int8Array(2);
        /**
         * 255
         * 0 1111-1111
         * 1 0000-0000
         * 1 0000-0001 -1
         */
        ints[1] = 255; 
        console.log(ints); //[ 0, -1]
    • Uint8ClampedArray(夹板数组类型)
      • 功能:不允许任何方向溢出
      • 数据范围:[ 0, 255]
      • 上溢:向下舍入为255
      • 下溢:向上舍入为0
      • const clampedInts = new Uint8ClampedArray([-1, 0, 255, 256])
        console.log(clampedInts); //[ 0, 0, 255 ,255];
      • 提示:不建议在canvas以外的开发中使用Uint8ClampedArray

6.4 Map

ES6中真正的键/值存储机制

6.4.1 基本API

  • Map()
    • 功能:Map构造函数
    • 参数:
      • 空映射:无参 
      • 有初始化:一个可迭代对象,需要包含键/值对数组
      • const m = new Map(); 
        console.log(m); //Map(0)
        const m1 = new Map([
          ['key1', 'val1'],
          ['key2', 'val2'],
          ['key3', 'val3']
        ]);
        console.log(m1); //Map(3)
        const m2 = new Map({
          [Symbol.iterator]: function* () { 
            yield ['key1', 'val1'];
            yield ['key2', 'val2'];
            yield ['key3', 'val3'];
          }
        })
        console.log(m2); //Map(3)
        const m3 = new Map([
          []
        ])
        console.log(m3) //Map(1){ undefined => undefined}
        console.log(m3.get(undefined)) //undefined
        console.log(m3.has(undefined)); //true
  • set()
    • 功能:添加键/值对
  • get()
    • 功能:根据键查询值
  • has()
    • 功能:查询键是否存在
  • size
    • 功能:获取映射中的键/值对数量
  • delete()
    • 功能:根据键删除存储的键值对
  • clear()
    • 功能:删除映射实例中所有的键值对
const m = new Map();
console.log(m.has('firstName')); //false
console.log(m.get('firstName')); //undefined
console.log(m.size); //0
m.set('firstName', 'Matt').set('lastName', 'Frisbie');
console.log(m.get('firstName')); //Matt
console.log(m.has('firstName')); //true
console.log(m.size); //2
console.log(m.delete('firstName')) //true
console.log(m.has('firstName')); //false
console.log(m.has('lastName')); //true
console.log(m.size); //1
m.clear();
console.log(m.size); //0

Map的键/值有如下特性:

  • 可以使用任何JavaScript数据类型作为键/值
  • Map内部使用SameValueZero(严格对象相等标准)比较操作
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');
console.log(m.get(functionKey)); //functionKey
console.log(m.get(symbolKey)); //symbolValue
console.log(m.get(objectKey)); //objectValue
console.log(m.get(function () { })) //undefined
  • 映射中使用键/值的集合类型,在内容或属性被修改时保持不变
const m = new Map();
const objKey = {},
      objVal = {},
      arrKey = [],
      arrVal = [];
m.set(objKey, objVal);
m.set(arrKey, arrVal);
objKey.foo = 'foo';
objVal.bar = 'bar';
arrKey.push('foo');
arrVal.push('bar');
console.log(m.get(objKey)); //{bar:'bar'}
console.log(m.get(arrKey)); //['bar']
  • 使用SameValueZero进行相等性比较存在缺陷
const m = new Map();
const a = 0 / "", //NaN
      b = 0 / "", //NaN
      pz = +0,
      nz = -0;
console.log(a === b); //false
console.log(pz === nz) //true
m.set(a, 'foo');
m.set(pz, 'bar');
console.log(m.get(b)); //foo
console.log(m.get(nz)); //bar

6.4.2 顺序与迭代

Map的键值对顺序与迭代具有如下特性:

  • Map实例会维护键值对的插入顺序
  • Map实例会提供一个迭代器Iterator
const m = new Map([
  ['key1', 'val1'],
  ['key2', 'val2'],
  ['key3', 'val3'],
])
console.log(m.entries === m[Symbol.iterator]); //true
/**
 * ['key1','val1']
 * ['key2','val2']
 * ['key3','val3']
 */
for (let pair of m.entries()) { 
  console.log(pair);
}
/**
 * ['key1','val1']
 * ['key2','val2']
 * ['key3','val3']
 */
for (let pair of m[Symbol.iterator]()) { 
  console.log(pair); 
}
const m2 = new Map([
  ['key1', 'val1'],
  ['key2', 'val2'],
  ['key3', 'val3']
]);
console.log([...m2]); //[['key1','val1'],['key2','val2'],['key3','val3']]
/**
 * key1 → val1
 * key2 → val2
 * key3 → val3
 */
m2.forEach((val, key) => console.log(`${key} → ${val}`)) 
console.log(Array.from(m2.keys()).join(','))//key1,key2,key3
console.log(Array.from(m2.values()).join(',')) //val1,val2,val3
  • 键值在迭代器遍历时是可以修改的,但映射内部的引用无法修改
const m1 = new Map([
  ['key1','val1']
])
for (let key of m1.keys()) { 
  key = 'newKey';
  console.log(key); //newKey
  console.log(m1.get('key1')); //val1
}
const keyObj = { id: 1 };
const m = new Map([
  [keyObj, 'val1']
]);
for (let key of m.keys()) { 
  key.id = 'newKey';
  console.log(key); //{id:'newKey'}
  console.log(m.get(keyObj)); //val1
}

6.4.3 选择object还是Map

Object和Map的区别主要在于内存和性能:

  • 内存占用
    • object与map的内存占用区别大小取决于各浏览器批量添加/删除键值对时内存分配的工程实现。
    • 所有浏览器存储键值对的内存数量与键的数量都是正比例线性关系
  • 插入性能
    • Map和Object插入新键值对的消耗一般相当
    • 浏览器插入Map一般会稍微快一点
  • 查找速度
    • 大型Object和Map中查找键值对性能差异极小
    • 少量键值对的情况下Object运算时间更短
    • 在把Object当成数组使用的情况下(使用连续整数作为属性),浏览器引擎可以进行优化(采用高效内存布局)
  • 删除性能
    • Object一般采用伪删除操作,即把属性值设置为undefined或null
    • 对于大多数浏览器引擎,使用Map进行delete操作更快

6.5 WeakMap(弱映射)

Weak:JavaScript垃圾回收程序对待“弱映射”中键的方式

弱映射:

  • 键特性:只能以Object或继承自Object的类型作为键,否则会抛出TypeError
    • 注意:对于原始值,如果进行原始类型包装后作为键值使用,不会报错
  • 值特性:没有限制

6.5.1 基本API

  • WeakMap()
    • 功能:创建WeakMap实例
    • 参数:
      • 无参:创建空WeakMap
        • const wm = new WeakMap();
          console.log(wm); //WeapMap{}
      • 带参数:
        • 类型:可迭代对象(键值对数组)
        • const wm = new WeakMap();
          console.log(wm); //WeakMap{}
          const key1 = { id: 1 },
                key2 = { id: 2 },
                key3 = { id: 3 };
          const wm1 = new WeakMap([
            [key1, 'val1'],
            [key2, 'val2'],
            [key3,'val3'],
          ])
          console.log(wm1.get(key1)); // val1
          console.log(wm1.get(key2)); // val2
          console.log(wm1.get(key3)); //val3
          const numberKey = new Number(10);
          const wm3 = new WeakMap([
            [numberKey,'val']
          ])
          console.log(wm3.get(numberKey)); //val
          const wm2 = new WeakMap([ //TypeError 
            [key1, 'val1'],
            ['BADKEY', 'val2'],
            [key3, 'val3']
          ]);
  • set()
  • get()
  • has()
  • delete()
const wm = new WeakMap();
const key1 = { id: 1 },
      key2 = { id: 2 };
console.log(wm.has(key1)); //false
console.log(wm.get(key1)); //undefined
wm.set(key1, 'Matt')
  .set(key2, 'Frisbie');
console.log(wm.has(key1)); //true
console.log(wm.get(key1)); //Matt
wm.delete(key1);
console.log(wm.has(key1)); //false
console.log(wm.has(key2)); //true

6.5.2 弱键

Weak-Key:弱键不属于正式的引用,不会阻止垃圾回收

注意:弱映射中值是被正式引用的,引用时不会被当做垃圾回收

const wm = new WeakMap();
/**
 * 没有指向该键值的引用
 * 所以这个对象键会被当做垃圾回收
 * 然后就从弱映射中消失, 
 * 值因为没有被引用,也会变成垃圾回收的对象
 * 从而wm变成一个空映射
 */
wm.set({}, 'val');
const wm = new WeakMap();
const container = {
  key: {}
};
wm.set(container.key, 'val');
function removeReference() { 
  container.key = null;
}
/**
 * 将键设置为null,即摧毁对象的最后一个引用
 * 拉伊回收程序就会把这个键值对清理掉
 */
removeReference();

6.5.3 不可迭代键

WeakMap键/值随时都可以被销毁,因此存在如下问题:

  • 不可能在不知道对象引用的情况下从弱映射中取得值,因此无法使用任何迭代键值对的方法
  • 不需要如clear这种一次性销毁全部键值的方法
  • 代码即便可以访问WeakMap实例,也无法看到其中的内容

6.5.4 使用弱映射

6.5.4.1 私有变量

WeakMap实现了JavaScript中实现真正私有变量的一种新方式,但是需要注意:

  • wm如果不使用闭包包装,外部代码只需要拿到对象实例的应用和弱映射就可以确定私有变量
  • 使用闭包包装wm可以防止外部访问,但是会使整个代码陷入ES6的闭包私有变量模式
const Person = (() => {
  const wm = new WeakMap();
  class Person {
    constructor(id) {
      this.idProperty = Symbol(id); //构造时创建id属性
      this.setPrivate('id', this.idProperty)
    }
    setPrivate(propertyName, value) {
      let privateMembers = wm.get(this) || {};
      privateMembers[propertyName] = value;
      wm.set(this, privateMembers);
    }
    getPrivate(propertyName) {
      return wm.get(this)[propertyName];
    }
  }
  return Person
})(); 

let user = new Person(123);
console.log(user.getPrivate('id')); //Symbol(123)
user.setPrivate('name', "Nick");
console.log(user.getPrivate('name')); //Nick
console.log(wm.get(user)); // ReferenceError:wm is not define

6.5.4.2 DOM节点元数据

对DOM树中DOM节点的引用,如果引用节点被改变(引用对象被删除),在这种情况下使用WeakMa不会妨碍垃圾回收

const m = new newMap();
/**
 * Map:DOM节点被删除后,由于存在引用,节点仍然都留在DOM树中
 * WeakMap:DOM节点被删除后,垃圾回收程序会立刻释放其内存
 */
const loginButton = document.querySelector('#login');
m.set(loginButton, { disabled: true });

6.6 Set

set是一种新集合类型,它更像是加强版的Map

6.6.1 基本API

  • new Set()
    • 参数:
      • 无参
        • 功能:创建一个空集合
        • const m = new Set();
          console.log(m); //Set(0)
      • 带参:
        • 功能:创建的同时初始化实例
        • 参数类型:可迭代对象
        • const s1 = new Set(['val1', 'val2', 'val3']);
          console.log(s1); //{'val1','val2','val3'}
          const s2 = new Set({
            [Symbol.iterator]: function* () {
              yield 'val111';
              yield 'val222';
              yield 'val333';
            }
          });
          console.log(s2); //{'val111','val222','val333'}
  • has
  • add
  • delete
  • clear
const s = new Set();
console.log(s.has('Matt')); //false
console.log(s.size); //0
s.add('Matt').add('Nick');
console.log(s.has('Matt')); //true
console.log(s.size); //2
s.delete('Matt');
console.log(s.has('Matt')); //false
console.log(s.size); //1
s.clear();
console.log(s.has('Nick')); //false
console.log(s.size); //0

Set类型的API具有以下属性:

  • 集合使用SameValueZero操作(类似于严格对象标准来检查)
const s = new Set();
const functionVal = function () { };
const symbolVal = Symbol();
const objectVal = new Object();
s.add(functionVal);
s.add(symbolVal);
s.add(objectVal);
console.log(s.has(functionVal)); //true
console.log(s.has(symbolVal)); //true
console.log(s.has(objectVal)); //true
console.log(s.has(function () { })) //false
  • 用作值的对象和其他集合类型在自己的内容或属性被修改时也不会改变
const s = new Set();
const objVal = {}, arrVal = [];
s.add(objVal);
s.add(arrVal);
objVal.bar = "bar";
arrVal.push('bar');
console.log(s); //{{bar:'bar'},['bar']}
  • add/delete皆为幂等操作,delete返回一个表示集合中是否存在要删除的值的布尔值
const s = new Set();
s.add('foo');
console.log(s.delete('foo')); //true
console.log(s.delete('foo')); //false

6.6.2 顺序与迭代

Set会维护值插入时的顺序,因此其迭代方法具有有序性,迭代相关的操作有以下特性:

  • 迭代相关方法
    • values 
    • keys:values的别名方法
    • [Symbol.iterator]:引用values取得这个迭代器
    • const s = new Set(['val1', 'val2', 'val3']);
      console.log(s.keys()); //SetIterator{'val1','val2','val3'}
      console.log(...s.values()) //val1 val2 val3
      for (let i of s[Symbol.iterator]()) { 
        console.log(i); //val1 val2 val3
      }
      console.log(s.values === s.keys); //true
      console.log(s.values === s[Symbol.iterator]); //true
      console.log(s.keys === s[Symbol.iterator]); //true
  • 默认迭代器:values
    • const s = new Set(['val1', 'val2', 'val3']);
      console.log([...s]); //['val1','val2','val3']
  • entries()
    • return:迭代器
    • 迭代元素:Array(item,item),即包含两个重复元素的数组
    • const s = new Set(['val1', 'val2', 'val3']);
      for (let pair of s.entries()) {
        /**
         * ['val1','val1']
         * ['val2','val2']
         * ['val3','val3']
         */
        console.log(pair); 
      }
  • forEach()
    • param1:迭代元素
    • param2:用于重写回调内部this的值
    • const s = new Set(['val1', 'val2', 'val3']);
      s.forEach((val, dupVal) => { 
        /**
         * val1 -> val1
         * val2 -> val2
         * val3 -> val3
         */
        console.log(`${val} -> ${dupVal}`);
      })
  • 修改集合中的值不会影响作为集合值的身份
    • const s1 = new Set(['val1']);
      for (let value of s1.values()) { 
        value = 'newVal';
        console.log(value);  //newVal
        console.log(s1.has('val1')) //true
      }
      const valObj = { id: 1 };
      const s2 = new Set([valObj]);
      for (let value of s2.values()) { 
        value.id = 'newId';
        console.log(value);  //{id:'newId'}
        console.log(s2.has(valObj)); //true
      }

6.6.3 定义正式集合操作

推荐使用子类化Set的方式进行集合操作,

即先在子类上实现静态方法,再在实例方法中使用这些静态方法。

需要注意以下几个地方:

  • 子类上的静态方法最好能支持处理任意多个集合实例
  • Set方法注意处理集合的顺序
  • 尽可能避免集合和数组之间的相互转换,因为能够节省转换时对象初始化的成本
  • 对于需要返回集合处理结果的方法,不要修改已有的集合实例,应该返回新的集合实例;
class XSet extends Set {
  union(...sets) { //并集
    return XSet.union(this, ...sets)
  }
  static union(a, ...bSets) {
    const unionSet = new XSet(a);
    for (const b of bSets) {
      for (const bValue of b) {
        unionSet.add(bValue);
      }
    }
    return unionSet;
  }
  intersection(...set) { //交集
    return XSet.intersection(this, ...set);
  }
  static intersection(a, ...bSets) {
    const intersectionSet = new XSet(a);
    for (const aValue of intersectionSet) {
      for (const b of bSets) {
        if (!b.has(aValue)) { 
          intersectionSet.delete(aValue);
        }
      }
    }
    return intersectionSet;
  }
  difference(set) { //差集
    return XSet.difference(this, set);
  }
  static difference(a, b) {
    const differenceSet = new XSet(a);
    for (const bValue of b) {
      if (a.has(bValue)) {
        differenceSet.delete(bValue);
      }
    }
  }
  symmetricDifference(set) { //对称差分
    return XSet.symmetricDifference(this, set);
  }
  static symmetricDifference(a, b) { 
    return a.union(b).difference(a.intersection(b));
  }
  cartesianProduct(set) { //笛卡尔积
    return XSet.cartesianProduct(this, set);
  }
  static cartesianProduct(a, b) {
    const cartesianProductSet = new XSet();
    for (const aValue of a) {
      for (const bValue of b) {
        cartesianProductSet.add([aValue, bValue]);
      }
    }
    return cartesianProductSet;
  }
  powerSet() { //幂集
    return XSet.powerSet(this);
  }
  static powerSet(a) {
    const powerSet = new XSet().add(new XSet());
    for (const aValue of a) {
      for (const set of new XSet(powerSet)) {
        powerSet.add(new XSet(set).add(aValue));
      }
    }
    return powerSet;
  }
}

6.7 WeakSet

Weak:描述的是JavaScript垃圾回收程序对待“弱集合”中值的方式

6.7.1 基本API

  • new WeakSet
    • 功能:实例化一个WeakSet
    • 参数:
      • 类型:只能是以Object或者继承自Object的类型为元素的可迭代对象
      • 注意:
        • WeakSet实例中值是具有顺序的
        • (全有/全无)只要有一个值无效就会导致整个初始化失败
        • const ws = new WeakSet();
          const val1 = { id: 1 },
                val2 = { id: 2 },
                val3 = { id: 3 };
          const ws1 = new WeakSet([val1, val2, val3]); 
          console.log(ws1.has(val1)); //true
          console.log(ws1.has(val2)); //true
          console.log(ws1.has(val3)); //true
          
          const ws2 = new WeakSet([val1, val2, 'BADVAL']) //typeError
          const stringVal = new String("val3");
          const ws3 = new WeakSet([stringVal]);
          console.log(ws3.has(stringVal)); //true
  • add()
  • has()
  • delete()
const ws = new WeakSet();
const val1 = { id: 1 },
      val2 = { id: 2 };
console.log(ws.has(val2)); //false
ws.add(val1).add(val2);
console.log(ws.has(val1)); //true
console.log(ws.has(val2)); //true
ws.delete(val1);
console.log(ws.has(val1)); //false
console.log(ws.has(val2)); //true

6.7.2 弱值

弱集合定义:“弱弱的拿着”,即值不属于正式的引用,不会阻止垃圾回收。

const ws = new WeakSet();
/**
 * 因为没有指向这个对象的其他引用,
 * 因此当这行代码执行完,该对象值就会被当成垃圾回收
 * 该值从弱集合中消失,该集合从而变成空集合
 */
ws.add({});
const ws = new WeakSet();
const container = {
  val:{}
}
ws.add(container.val);
/**
 * 调用该函数就会摧毁值对象的最后一个引用
 */
function removeReference() {
  container.val = null;
}

6.7.3 不可迭代值

WeakSet限制只能使用对象作为值,致使其有以下特性:

  • 由于其值随时都可能被销毁,因此没有必要提供迭代值的能力
  • WeakSet同样不需要clear()这样一次性销毁所有值的方法
  • 由于不能迭代,所以必须使用引用的方式取得弱集合中的值

6.7.4 使用弱集合

一般在给对象打标签时使用弱集合

const disabledElements = new WeakSet();
const loginButton = document.querySelector('#login');
/**
 * 将节点加入禁入集合,
 * 如果元素从DOM树中被删除,
 * 垃圾回收程序会自动对无效引用进行回收
 */
disabledElements.add(loginButton);

6.8 迭代与扩展操作

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

  • Array
  • TypedArray
  • Map
  • Set

这就意味着这些类型都支持顺序迭代,包括:

  • for-of循环
    • let iterableThings = [
        Array.of(1, 2),
        typeArr = Int16Array.of(3, 4),
        new Map([[5, 6], [7, 8]]),
        new Set([9, 10])
      ]
      for (const iterableThing of iterableThings) {
        for (const x of iterableThing) {
          /**
           * 1 2 
           * 3 4
           * [5,6] [7,8]
           * 9 10
           */
          console.log(x);
        }
      }
  • 扩展操作符
    • 在执行浅复制时有效
    • 浅复制:只会复制对象引用
    • 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
      
      let arr3 = [1, 2, 3];
      let arr4 = [0, ...arr3, 4, 5];
      console.log(arr4); //[0,1,2,3,4,5]
      
      let arr5 = [{}];
      let arr6 = [...arr5];
      arr5[0].foo = 'bar';
      console.log(arr6[0]); //{foo:'bar'}
  • 参数为可迭代对象的构造函数
    • let map1 = new Map([[1, 2], [3, 4]]);
      let map2 = new Map(map1);
      console.log(map1); //Map{1=>2,3=>4}
      console.log(map2); //Map(1=>2,3=>4)
  • 多种构建方法
    • Array.of()
    • Array.from()
    • let arr1 = [1, 2, 3];
      let typedArr1 = Int16Array.of(...arr1);
      let typedArr2 = Int16Array.from(arr1);
      console.log(typedArr1); //Int16Array[1,2,3]
      console.log(typedArr2); //Int16Array[1,2,3]
      
      let map = new Map(arr1.map(x => [x, 'val' + x]));
      console.log(map); //{1=>'val1',2=>'val2',3=>'val3'}
      let set = new Set(typedArr2);
      console.log(set); //Set{1,2,3}
      let arr2 = [...set];
      console.log(arr2); //[1,2,3]
posted @ 2022-10-26 17:08  Electric-Duck  阅读(32)  评论(0)    收藏  举报