JavaScript高级程序设计第四版--第六章--集合引用类型

106 阅读14分钟

Object

Object是ECMAScript中最常用的类型之一,虽然Object类型的实例没有什么功能,但是很适合在存储和在应用程序之间交换数据

new操作符创建

let person = new Object();

字面量创建

使用字面量方式创建时,并不会实际调用Object构造函数

let person = {}; 

属性存取

属性一般通过点语法来存取的,但也可以使用中括号加字符串的方式来存取

Array

ECMAScript数组也是一组有序的数据,与其它语言不同的是,数组中每个槽位可以存储任意类型的数据,并且数组也是动态大小的,会随着数据添加而自动增长

new操作符创建

let ar = new Array() // 创建长度为0的数组
let ar1 = new Array(1) // 创建长度为1的数组
let ar2 = new Array(2,) // 创建长度为1的数组
let ar3_4 = new Array(3,4) // 创建长度为2的数组,元素为3、4

字面量创建

使用字面量方式创建时,并不会实际调用Object构造函数

let ar = []// 创建长度为0的数组
let ar1 = [1]// 创建长度为1的数组,元素为1
let ar2 = [2,]// 创建长度为1的数组,元素为2

数组空位

在使用字面量初始化数组时,可以使用一串逗号来创建空位

let ar = [,,,]  // 三个空元素

需要注意的是,ES6之前的方法会忽略这个空位,但具体的行为也会因方法而异,由于存在差异在实践中要避免使用数组空位

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

from

用于将类数组结构(任何可迭代的结构或者有一个length属性和可索引元素的结构)转换为数组实例

let ob = { export: 1 }
let ar = Array.from("Math", function (e) {
    this.export = 3
    return e + this.export
}, ob) // 参数2,3为可选,需要注意的是对this.export修改会导致ob的修改
console.log(ar, ob) // ['M3', 'a3', 't3', 'h3'] {export: 3}

of

用于将一组参数转换为数组实例

let ar = Array.of('M','a','t','h') // ['M', 'a', 't', 'h']

索引

需要注意的是数组的length属性不是只读的,可以将length属性修改为非负整数,最多可以包含4294967295个元素

let ar = [1,2,3,4]
ar.length = 1
console.log(ar) // [1]
ar.length = 4
console.log(ar) // [1, 空 ×3] 注意2、3、4的数据丢失

检测数组

ECMAscript提供了Array.isArray方法来确定一个值是否为数组,而不管它是在哪个全局执行上下文中创建的

迭代器方法

keys()

返回数组索引迭代器

values()

返回数组元素迭代器

entries()

返回键值对的迭代器

fill()

fill函数用于填充数组,接收负数为参数

let ar = [1,2,3,4,5];
ar.fill(5)      // 用5填充整个数组
ar.fill(6,3)    // 用6填充索引大于3的元素
ar.fill(7,1,3)  // 用7填充索引大于等于1小于3的元素

copyWithin()

copyWithin会按照指定范围浅复制数组中的部分内容,然后将它们插入到索引开始的位置。JavaScript引擎在插值前会完整复制范围内的值,因此在复制期间不存在重写的风险

let arr1 = ['a', 'b', 'c', 'd', 'e'];
arr1.copyWithin(0, 3, 4);
console.log(arr1);  //["d", "b", "c", "d", "e"]

toLocaleString()

调用每个元素的toLocaleString方法并将返回值以逗号分隔的字符串

toString()

调用每个元素的toString方法并将返回值以逗号分隔的字符串

valueOf()

返回数组本身

join()

以参数作为分隔符

如果数组中的某一项是null或undefined,则在join()、toLocaleString()、toString()和valueOf()返回的结果中以空字符串表示

栈方法

push()

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

pop()

用于删除数组的最后一项,同时减少数组的length值,返回被删除的项

队列方法

shift()

用于删除数组的第一项并返回它,然后数组长度减一(每个元素下标都要减一)

unshift()

在数组开头添加任意数量元素,然后返回新的数组长度(每个元素下标都会递加)

排序方法

reverse()和sort()方法都会修改数组本身

reverse()

翻转数组

sort()

按照升序对数组元素(通过字符串顺序来决定,所以会调用元素的toString方法)进行排序

sort方法可以接收一个函数作为参数,参数1为第n+1个元素,参数2为第n个元素

如果第一个参数在第二个参数前面,就返回负值

如果两个参数相等,就返回0

如果第一个参数在第二个参数后面,就返回正值

在排序数组时会使用一种叫做 "Timsort" 的排序算法。Timsort 是由 Tim Peters 在 2002 年设计的,是一种稳定的混合排序算法,它将归并排序和插入排序结合在一起,以提高效率。它的名字来自 "Tim" 和 "merge sort",表示这是一个由 Tim Peters 设计的归并排序。

concat()

在现有数组全部元素基础上创建一个新数组

let ar = [1,2,4,3];
let ar1 = [15]
ar1[Symbol.isConcatSpreadable] = false  // 设置不允许打平数组
console.log(ar.concat(ar1))

slice()

创建一个包含原有数组中一个或多个元素的新数组

splice()

需要注意的是splice是对数组本身进行操作

删除

splice(0,1):删除第一个元素

插入

splice(2,0,'1'):将'1'插入到下标2元素的后面

替换

splice(0,1,'1'):用‘1’替换第一个元素

严格相等

在进行查找时会使用'==='进行比较

indexOf()

从前往后查找,找到返回下标否则返回-1

lastIndexOf()

从后往前查找,找到返回下标否则返回-1

includes()

从前往后查找,返回布尔值

断言函数

find()

该函数接收一个函数为参数和一个可选的参数2用于指定参数1函数的this。如果参数1函数返回true则find终止

迭代方法

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

every()

每一项函数都返回true,则这个方法返回true

filter()

对返回true的项,会被组成新数组返回

forEach()

对数组的每一项都执行函数,没有返回值

map()

每一项都执行传入的函数,返回由函数返回值组成的数组

some()

每一项都执行传入的函数,如果有一项函数返回true,则这个方法返回true,后续不再执行

归并方法

reduce、reduceRight只是遍历的方向不一样,接收一个函数和一个可选的并归起始值

let ar = [1,2,4,3];
ar.reduce((prev,cur,index,ar)=>{
    console.log(prev)
    return prev+cur
},10)

reduce()

reduceRight()

定型数组

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

定型数组可以直接传给底层图形驱动程序API,也可以直接从底层获取到

定型数组一旦创建,就不能再调整大小

历史

在WebGL的早期版本中,因为javascript数组与原生数组之间不匹配,所以出现了性能问题。图形驱动程序API通常不需要以javaScript默认双精度浮点格式传递给它们的数值,而这恰恰是JavaScript数组在内存中的格式。因此,每次WebGL与JavaScrirpt运行时之间传递数组时,WebGL绑定都需要在目标环境分配新数组,以其当前格式迭代数组,然后将数值转型为新数组中的适当格式,而这些要花费很多时间。

ArrayBuffer

允许JavaScript运行时访问一块名为ArrayBuffer的预分配内存

ArrayBuffer是所有定型数组及试图引用的基本单位,不能仅通过对ArrayBuffer引用直接读取值,需配合DataView使用操作读取数据

SharedArrayBuffer是ArrayBuffer的一个变体,可以无须复制就在执行上下文间传递它

与malloc()区别

ArrayBuffer在某种程度上类似与C++的malloc函数,但也有几个明显的区别

mallocArrarBuffer
在分配失败时会返回一个null指针在分配失败时会抛出错误
可以利用虚拟内存,因此最大可分配内存受寻址系统限制分配的内存不能超过Number.MAX_SAFE_INTEGER(2的53方-1)字节
调用成功不会初始化内存所有二进制位初始化为0
分配的堆内存除非调用free或程序退出才能释放分配的堆内存可以被当垃圾回收,不用手动释放

DataView

通过DataView视图可以操作ArrayBuffer,这个视图专为文件i/o和网络i/o设计,其API支持对缓冲数据的高度控制,但相比与其它试图性能也会差一些。对缓存内容没有任何预设,也不能迭代。默认是大端模式

ElementType

虽然DataView对存储在缓冲区内的数据类型没有预设,但是它暴露的API强制开发者在读、写时指定一个ElementType,然后DataView就会诚实地读、写而完成相应的转换。ECMAScript6支持8种不同的ElementType

ElementType字节说明等价C类型取值范围
Int818位有符号整数signed char-128~127
Uint818位无符号整数unsigned char0~255
Int16216位有符号整数short-32768~32767
Uint16216位无符号整数unsigned short0~65535
Int32432位有符号整数int-2147486648~2147483647
Uint32432位符号整数unsigned int0~4294967295
Float32432位IEEE-754浮点数float-3.4e+38~3.4e+38
Float64864位IEEE-754浮点数double-1.7e+308~+1.7e+308

DataView为上表中的每种类型都暴露了get和set方法,这些方法使用byteOffset定位要读取或写入的位置,并且类型是可以相互使用的

const buf = new ArrayBuffer(2);
const view = new DataView(buf);
console.log(view.getInt8(0));
console.log(view.getInt16(0));
view.setUint8(0,255);
view.setUint8(1,0xff);
console.log(view.getInt16(0))
字节序

JavaScript运行时所在系统的原生字节序决定了如何读取或写入字节,但DataView并不遵守这个约定。对一段内存而言,DataView是一个中立接口,它会遵循你指定的字节序。DataView的所有api方法都默认以大端字节序列,但接收一个可选的布尔参数,设置为true即可开启小端字节序列

const buf = new ArrayBuffer(2);
const view = new DataView(buf);
​
view.setUint8(0,255,true);
view.getUint8(0,true);
边界情况

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

DataView在写入缓冲时,会尽最大努力把一个值转换为合适的类型,后备为0。如果无法转换,则抛出错误

ElementTypeArray

特定于一种ElementType(同DataView中介绍的ElementType) 且遵循系统原生的字节序,提供了适用面更广的API和高性能

读取已有缓冲区创建

const buf = new ArrayBuffer(12);
const int32Ar = new Int32Array(buf)
console.log(int32Ar.length)
console.log(int32Ar.buffer.byteLength)

自有缓冲区创建

const int32Ar = new Int32Array(6)
console.log(int32Ar.length)
console.log(int32Ar.buffer.byteLength)

填充创建

const int32Ar = new Int32Array([1,2,34])
console.log(int32Ar.length)
console.log(int32Ar.buffer.byteLength)

与js数组区别

拥有js数组的非变异方法,通过拥有set和subarray两个特有方法

set()

从数组或定型数组中把值复制到当前定型数组中的指定索引位置

const container = new Int16Array(8)
container.set(Int16Array.of(1,2,3,4),1)
console.log(container)

subarray()

从原始定型数组中复制一个新的定型数组,复制的开始和结束都是可选的

const container = Int16Array.of(1,2,3)
const copy = container.subarray(1,3)
console.log(copy)

上溢下溢

对上下溢出处理都是直接截取靠右边的二进制,存取都是补码形式

Uint8ClampedArray

除了前面介绍的8种类型,还有一种“夹板”数组类型,不允许任何方向溢出。超出最大值255会被取舍为255,超出最小值0会被向上舍入为0

const clampedArray = new Uint8ClampedArray([-1,256,255]);
console.log(clampedArray);

Uint8ClampedArray完全是HTML5 canvas元素的历史遗留,除非真的做canvas相关的开发,否则不用使用它

Map

ECMAScript6以前,在JavaScript中实现“键值对”式存储可以使用Object来方便高效地完成,也就是使用对象属性作为键,再使用属性来引用值。但这种实现并非没有问题,为此TC39委员会专门为“键/值”存储定义了一个规范。

初始化

想在创建的同时初始化实例,可以给map构造函数传入一个可迭代对象,需要包含键/值对数据。可迭代对象中的每个键/值对都会按照迭代顺序插入到新映射实例中

const m1 = new Map([
    ["k1","v1"],
    ["k2","v2"]
])
console.log(m1.size)

方法

has

map实例上是否拥有传入的键

get

获取传入的键对应的值,如果没有则返回undefined

set

覆盖设置传入的键值对,支持链式操作

forEach

通过forEach方法可以迭代键值对

keys

以数组形式返回映射副本

values

以数组形式返回映射副本

delete

根据传入的键,删除对应的键值对

clear

清除Map中的所有元素

属性

size

返回键值对的数量

与Object差异

1. 与Object只能使用数值、字符串或符号作为键不同,Map可以使用任何JavaScript数据作为键。Map内部使用SameValueZero比较操作(ECMAScript规范内部定义,语言中不能使用),基本上相当于使用严格对象相等的标准来检查键的匹配性

let myMap = new Map()
let nm = 2;
myMap.set(nm,1)
myMap.get(nm)
console.log(myMap);

2. Map实例会维护键值对的插入顺序(覆盖赋值也不会改变这个顺序),映射实例可以提供一个迭代器,能够以插入循序生成[key,value]形式的数组

let myMap = new Map([
            ['k1','v1'],
            ['k2','v2']
        ])
console.log(myMap.entries === myMap[Symbol.iterator])   // true

3.内存占用给定固定大小的内存,Map大约可以比Object多存储50%的键/值对

4.插入性能代码涉及大量插入操作,那么Map的性能更佳

5.查找速度,代码设计大量的查找操作,某些情况下Object更好些

6.删除性能,Map的delete操作比插入何查找更快,代码涉及大量的删除操作,Map的性能更佳

WeakMap

ECMASCript6新增的“弱映射”是一种新的集合类型,为这门语言带来了增强的键/值对存储机制。WeakMap是Map的“兄弟”类型,其API也是Map的子集。WeakMap中的“weak”描述的是JavaScript垃圾回收程序中对待“弱映射”中键的方式

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

只是键是弱引用,值不是弱引用(弱引用:不是正式的引用,不会阻止垃圾回收)

不可迭代键

因为WeakMap中的键/值对任何时候都可能被销毁,所以没必要提供迭代器键/值对的能力

DOM节点元数据

因为WeakMap实例不会妨碍垃圾回收,所以非常适合保存关联元数据

const vm = new WeakMap()
const loginButton = document.querySelector('#login');
vm.set(loginButton,{disabled:true}) // 当loginButton节点从页面移除时,自动释放内存,如果是Map需要手动释放对象的引用

Set

ECMAScript6新增的Set是一种集合类型,为这门语言带来了集合数据结构。Set再很多方面都像加强的Map,这个因为它们的大多数API和行为都是共有的

和Map类似,Set可以包含任何JavaScript数据类型作为值,使用Same Value Zero操作进行比较

初始化

在创建时同时初始化Set,需要在构造函数传入一个可迭代对象,其中需要包含插入到新集合实例中的元素

const s1 = new Set([1,2])

方法

add

支持链式操作

delete

has

clear

values、keys、[Symbol.iterator]

返回一个维护插入顺序的迭代器

entries

返回两个元素都是相同的迭代器

forEach

迭代的两个元素都是相同的

属性

size

WeakSet

考核Map和WeakMap的区别

扩展操作符

扩展操作符在对可迭代对象执行浅复制时特别有用,只需简单的语法就可以复制整个对象

let ar1 = [1,2,3];
let ar2 = [...ar1];