每个系列一本前端好书,帮你轻松学重点。
本系列来自曾供职于Google的知名前端技术专家马特·弗里斯比 编写的 《JavaScript高级程序设计》(第5版)
世界上很少有事物孤立存在,即便你是单身,世界上有很多单身...
所以,集合(列表)是一种很常见的数据形式,新闻列表、人员名单列表等。
本篇文介绍JavaScript中的这类数据。
集合的共性
你一眼就能看出什么样的数据是集合,就像这样:
1,2,3,4,5
张三,李四,王五
所以必然存在共性,如:包含多个同类型的值(即便可以不同),能够遍历、增加、删除、查找、修改。
但往往因为集合类型的不同,适用场景也不同。
数组
数组是最常见的集合类型,不仅JavaScript,其他语言同样,但在JavaScript中,它有其特殊性,它的每个索引位可以存储不同类型的数据,而且是动态大小,会随着数据增加自动增长。
数组定义
定义数组有若干方法,以下都是可行的:
javascript
let colors = new Array(); // 不知数量
let colors = new Array(20); // 已知数量
let colors = new Array("red","blue"); // 直接传入元素
let colors = ["red","blue"]; // 字面量法
除此之外,还有两个方法,可以将看起来像数组,但本身不是数组的数据转换成数组,就是from()、of()。
什么叫"像而不是",比如类数组,当我们通过DOM API document.getElementsByTagName()等获取到一组NodeList,就是类数组。
类数组只具备一部分数组的能力,有length属性,可通过索引访问元素,但不能正常使用数组的方法,如果需要,就可以用Array.from()方法将其转换为数组再使用。
of()方法则用于将一组参数转换为数组,如 Array.of(1,2,3,4),结果是 [1,2,3,4]。
数组方法
数组如此常用,肯定存在很多情况需要处理,于是JavaScript赋予了它一众强大的方法,这些方法为实际项目中各种需求提供了便利,此处介绍一部分。
数组项方法
less
let colors = ["red","blue"]; // 原本是这样
colors.push("yellow")
["red","blue","yellow"]; // 操作后
除此之外,还有shift(取第一项)、unshift(头部添加)、pop(取最后一项)
迭代方法
包括forEach、map、filter等,它们对数组的每一项运行其中的函数体,并根据各自的能力返回相应结果。
forEach遍历数组项,没有返回值,类似for循环。
map返回代码逻辑中指定的返回值,组成新的数组。
filter返回代码中条件判断为true的数组项,组成新的数组。
dart
let numbers = [2,1,3]
let newNumbers = numbers.map(num=>{
return num + 1
})
// [3,2,4]
let newNumbers = numbers.filter(num=>{
return num > 1
})
// [2,3]
除以上方法外,数组还有很多其他实用方法,如"reverse(反转)、sort(排序)、reduce(归并)、includes(包含)、flat(展开)"等,相当丰富,可满足各种需求,篇幅原因不一一介绍,各位根据需要拓展学习。
笔者在最初学习时,曾苦于方法过多难以记忆,现在告诉你,不用记,没有比实践带来的印象更深,用到的时候多查,用多了自然记住。
定型数组
JavaScript中本没有定型数组(Typed Array),它的行为也与普通数组有所差异,它是什么,又为何存在?
定型数组指的是一种特殊的包含数值类型的数组。设计的目的是提升向原生库传输数据的效率,如:WebGL。
就是说,JavaScript会与WebGL产生交互,但二者的数据格式不同,需要额外处理,这就带来更多消耗。
当你看到Float32Array,Int32Array、ArrayBuffer、DateView等略感陌生的数据时,就代表定型数组出现了。
ArrayBuffer是所有定型数组的基本单位,当使用WebAssembly时,可能见到ShareArrayBuffer,它是ArrayBuffer的一种变体。
定型数组的应用尚不广泛,初步了解这就够了。
Map
Map是ES6中加入的新的集合类型,它带来了真正的键-值存储机制。
什么叫"真正",还有假的?
很长一段时间内,这个角色都由Object在扮演。
编码的灵活性决定了一些代码的功能可用另一种替代,这就是典型的例子,Map的多数特性Object都可以实现,但是专一职责原则又告诉我们它该有更确切的实现。
基本API
dart
const m = new Map();
m.set("name","说书匠").set("age",18);
m.has("name"); // true
m.get("name"); // "说书匠"
m.size // 2
m.delete("age") // age被删
m.clear(); // map清空
由上可见,Map中添加、判断和查找键值是很直观的,你或许发现了第二行的特殊之处,set后面又跟了个set,因为set本身会返回映射的实例,就又可以接着用了。
与Object只能用数值、字符串或符号作为键不同,Map可以使用任意JavaScript数据类型作为键,值也没有限制。
除此之外,在迭代方面也有差异,Map实例会维护键-值对的插入顺序,可以根据插入顺序执行迭代操作。可用的方法有keys()、values()、entries()等。
它们对于内存占用的情况也不同,固定内存情况下,Map可以比Object多存储50%左右的键-值对。
在插入、查找、删除等操作的性能上,Map也比Object更优,所以,需要用到键-值对的时候,我们可以调整一下编码习惯,优先选择Map。
Set
Set是另一种新增的集合型数据结构,像加强版的Map,因为它们多数API是相似的,正因如此,学习起来较为轻松。
基本API
scss
const m = new Set([1,2,3])
m.size // 3
m.add(4).add(5)
m.has(3) // true
m.delete(1)
m.clear()
操作同样直观易懂,不赘述。
既然是集合,也能迭代,Set的迭代也是支持顺序的,同样支持keys()、values()、entries()等。
其实,从形式上看,Set更像Array,没有键,只有值,那么这两者间又该如何选用呢?
每种数据结构的选择都应有原因,不妨做个对比。
适合Set场景:
1、需要存储不重复的唯一值
2、频繁检查元素是否存在
3、需要数学集合运算
4、需要频繁增删
适合Array场景:
1、需要有序
2、索引访问。
必要时,二者还可相互转换:
sql
// Array转Set
const m = new Set([1,2,3])
// Set转Array
[...m]
最后这个"..."操作是什么?
三个点也是一种操作符,叫"扩展"操作符,它可用于普通的对象、数组和Set这种集合类数据中,相当于把后面变量包含的值放到当前"容器"中。
同时,集合类型的数据都支持 for-of 循环,对于迭代访问每个值就方便多了。
弱引用
这是与JavaScript中垃圾回收相关的一个机制,我们定义的变量,引擎会自动管理它们占的内存,通常来说,当某个对象不再使用,垃圾回收器会释放它占的内存。
如果我们想保持对对象的引用,同时不阻止它被回收的行为,这种引用就称为"弱引用"。
弱引用的创建方式有三种:WeakRef、WeakMap、WeakSet 。分别对应Object、Map和Set。
除了不阻止垃圾回收,它们在特性和操作上跟前面介绍的也有区别。
WeakMap和WeakSet中的键只能是Object或者继承自Object的类型,同时,它们不可迭代,没有clear()方法。
小结
集合类型之常见,每个项目,甚至每个文件,都会有它们的身影,其中以数组最为常用,所以,掌握它们是必须的。
但正如前文所述,集合在某种程度上是对数据的存储和管理,像载体,最终,我们还是要拿到具体的每个值去做分析和处理,这时候,增删改,迭代方法,就很重要了,恰恰JavaScript提供了非常丰富又实用的方法,希望大家多加练习,熟练掌握,在开发时就能游刃有余。
更多好文第一时间接收,可关注公众号:"前端说书匠"