JavaScript中的集合入门

135 阅读10分钟

简介

不同形式的_数据组是_大多数编程语言的基本数据结构之一。在许多情况下。 通过不同的数据类型表达的数据组被称为 集合.

在本指南中,我们将看一下_JavaScript中的集合_,以及何时使用哪种类型的集合。我们要看的三个主要集合组是

  • 索引型集合
  • 键控集合
  • DOM集合

索引型集合

索引型集合_是一个数据的集合,它是_由它们的索引列出的。JavaScript的集合索引是_基于0的_,这意味着它们从0 ,而不是1 ,然后上升到n-1n 是集合中的对象数量。JavaScript有两种索引的集合Arrays和TypedArrays。

阵列对象

JavaScript中的Array 对象是一个有序的列表,其元素可以使用索引来访问。在JavaScript中,有多种创建Array 对象的方法,而且在结构上没有太大的区别。

let myArray1 = [x1, x2, ... , xN];
let myArray2 = new Array(x1, x2, ... , xN);
let myArray3 = Array(x1, x2, ... , xN);

JavaScript中的数组不是基于类型的,这意味着你不需要事先定义数组的类型,也不需要只添加同质元素。

let myArray = ["one", 1, "two", 2];

这是完全有效的语法,而且数组很乐意存储对字符串和整数的引用。让我们快速重新澄清一下数组的索引到底是什么。

let myArray = ["one", 1, "two", 2];
//				 0    1	   2	3	 --> index values

所以从0 开始,一直到n-1 ,其中n 是数组的长度。

每个数组都有一个名为length 的属性。一个数组的_长度_是在数组被初始化的同时确定的。因此,当我们创建一个数组时,一个值被分配给它的length 属性。

如果我们console.log(myArray.length) ,输出将是4

此外,改变_长度_将改变数组。你可以很容易地通过缩短一个数组的长度来截断它,并通过延长它来扩展它。

let myArray = ["one", 1, "two", 2];
console.log(myArray);

myArray.length = 5;
console.log(myArray);

myArray.length = 1;
console.log(myArray);

这就导致了。

["one", 1, "two", 2]
["one", 1, "two", 2, undefined]
["one"]

在JavaScript中,你可以创建一个没有任何元素,但有一定长度的数组。你可以把它看作是提前_分配_(预留)内存的东西。当我们通过改变一个数组的length ,使其大于之前的长度时,这正是启动的作用。

由于有三种方法来创建充满元素的数组,也有三种方法来创建分配内存的空数组,像这样。

let myArray1 = new Array(4); // creates an array with 4 empty spaces
console.log(myArray1.length); // 4

let myArray2 = Array(4); // similar to the previous one, just without the keyword new
console.log(myArray2.length); // 4

let myArray3 = [];
myArray3.length = 4 // this one is a bit different, we assign the value to the property length
console.log(myArray3.length); // 4

到目前为止,除了语法上的变化,这三种创建数组的方法没有任何区别。

但是,如果你想创建一个有单个元素的数组,这个数组是Number ,你就必须使用方括号并定义具体元素,而不是数组的大小。

这是因为如果你向Array 构造函数传递一个数字,你将创建一个空数组并分配那么多空间。

// New array with 10 spaces
let myArray1 = new Array(10)
// New array with a single element
let myArray3 = [10]

向数组添加元素

我们已经看到了如何创建一个Array ,不管它是空的还是非空的。现在让我们看看如何向它添加新元素。由于我们正在处理_索引集合_,我们将使用索引进行操作。

由于我们已经创建了一个有4个空元素的Array ,让我们用它来工作。要添加一个元素,我们所要做的就是_通过其索引访问该元素_,并为其_赋值_。

let myArray1 = new Array(4)

myArray1[0] = "one"
myArray1[1] = "two"
myArray1[2] = "three"
myArray1[3] = "four"

console.log(myArray)

这将是输出结果。

['one', 'two', 'three', 'four']

尽管我们在创建数组时为元素分配了4个空间,但在JavaScript中,Array's是动态生成的,这意味着你可以在任何时候缩小或扩大它们。

这意味着我们可以在我们的Array ,尽管我们用4个空格给它 "镶边"。

myArray1[4] = "five"
myArray1[5] = "six"

console.log(myArray) // Output: ['one', 'two', 'three', 'four', 'five', 'six']

我们可以很容易地使用for 循环或forEach 循环来迭代一个数组。

console.log('Traditional for loop:')
for (let i = 0; i < myArray1.length ; i++) {
	console.log(myArray1[i]);
}

console.log('Functional forEach loop:')
myArray1.forEach( function (element){ console.log(element);});

这将输出。

Traditional for loop:
one
two
three
four
five
six

Functional forEach loop:
one
two
three
four
five
six

数组方法

现在我们已经掌握了一些技巧,让我们来试验一下JavaScript中内置的Array 方法。你已经在前面的例子中看到了一个.forEach() 循环被调用到myArray1

让我们来看看最常用的方法。

  • push() - 在一个数组的末尾添加一个元素
let myArray = [1,2,3];
myArray.push(4);
console.log(myArray); // outputs [1, 2, 3, 4]
  • pop() - 删除一个数组的最后一个元素
let myArray = [1,2,3,4];
myArray.pop();
console.log(myArray); // outputs [1, 2, 3]
  • concat() - 将数组(两个或多个)连接成一个数组
// Concating 2 arrayslet myArray1 = [1,2,3]
let myArray2 = [4,5,6];
let finalArray1 = myArray1.concat(myArray2);
console.log(finalArray1); // [1,2,3,4,5,6]
    
// Concating 3 arrayslet 
myArray3 = [7,8,9];
let finalArray2 = myArray1.concat(myArray2, myArray3);
console.log(finalArray2); // [1,2,3,4,5,6,7,8,9]
  • join(delimiter) - 将所有的元素连接成一个字符串,以a为界。delimiter
let myArray = ["Earth", "Wind", "Fire"];
let arrayString = myArray.join(",");
console.log(arrayString); // outputs Earth, Wind, Fire
// Bonus example
console.log(arrayString + "- September"); // outputs Earth, Wind, Fire - September
  • reverse() - 正是这样,颠倒了数组中元素的顺序
let myArray = [1,2,3];
let reversed = myArray.reverse();
console.log(reversed); // [3,2,1]
  • slice(start, end) - 复制一个数组的一部分,从索引start 开始,直到索引。end-1
let myArray = [1,2,3,4,5,6];
myArray = myArray.slice(3, 5);
console.log(myArray); // [4,5]

_TypedArray_对象

Array 对象是处理JavaScript中任何数据类型的完美选择,因为它可以在一个数组中存储不同类型的元素,并且有强大的方法来操作这些元素。

然而,当需要处理原始二进制数据时--这时TypedArray 对象就发挥作用了。例如,在操作音频和视频时,原始数据会被处理。

_TypedArray_对象的结构

JavaScript类型的数组被划分为 缓冲区视图.缓冲区_是一个只存储一大块数据的对象,没有任何方法来访问或操作这些数据。为了实现这一点,你必须使用_视图--它提供了一个_上下文_,一个数据类型,将数据变成TypedArray

_缓冲区_是通过一个ArrayBuffer 对象实现的。它被用来表示一个固定长度的二进制数据缓冲区。为了表示这个缓冲区,我们必须创建一个视图--DataView ,它以选择的格式表示该缓冲区。有各种类型的视图,代表最常见的数字类型。

  • Int8Array - 数值范围 [-128, 127]
  • UInt8Array - 数值范围 [0, 255], u代表_无符号_
  • Int16Array - 数值范围 [-32768, 32767]
  • UInt16Array - 数值范围 [0, 65535]
  • Float32Array - 值范围 [1.2E-38, 3.4E38]

创建一个_类型化的Array_

当创建一个特定类型的TypedArray 对象时,我们实现了我们之前谈到的--创建一个缓冲区和一个视图。TypedArray 对象没有明确的构造函数--没有new TypedArray() 语法--我们直接实例化我们需要的数组类型。

let tArray = new Int8Array(8);

在这里,我们为一个大小为8字节的Int8Array ,创建了一个缓冲区和一个视图。对元素的赋值与Array 对象的赋值相同。

tArray[0] = 10;
console.log(tArray);

这将输出。

Int8Array [ 10, 0, 0, 0, 0, 0, 0, 0 ]

这样,我们可以在TypedArray 中填入通常存在的值,但不限于处理音频或视频时--但这是一个全新的文章的主题。

有键集合

键控集合是一个数据的集合,用符号表示。 _键值_符号表示的数据集合。元素的值是通过它们各自的键来访问和操作的。

在JavaScript中,有两种类型的键值集合:MapSet

在JavaScript中,Maps和Sets都可以有一个单一的_值_归于一个_键_,尽管你可以通过归于一个List ,作为一个值,包含多个元素来破解它。仍然值得注意的是,List 本身就是一个值--而不是它的组成元素。

此外。 键必须是唯一的.

地图对象

JavaScript中的一个Map 对象是一个标准的地图,包含了 键-值对.要创建一个新的Map 对象,我们只需调用构造函数。

let myMap = new Map();

将元素添加到地图中

一个空的地图对我们没有什么好处。让我们通过set() 方法添加一些元素,该方法接受一个必须是字符串的key_name ,以及一个可以是任何类型的value

myMap.set("one", 1);
myMap.set("two", 2);
myMap.set("three", "three");
console.log(myMap);

地图也是异质的,所以你不需要为所有的键设置相同的值类型。

Map { 'one' => 1, 'two' => 2, 'three' => 'three' }

访问地图的元素

要访问一个Map的元素,我们只需get() ,并传入key_name ,因为这些是Map中的唯一标识符。

console.log(myMap.get("two")); // Output: 2

由于这个集合_不是_基于索引的,我们不能使用方括号来访问一些值:myMap["two"] 将返回一个undefined 的值。

然而,如果我们在_不存在的键_上调用get(key_name) 方法,返回值也将是undefined

地图方法

你将使用地图的主要两个方法是:get()set() ,但你也想对它们进行迭代。Map 类也有一个forEach() ,它可以很容易地用于对所有条目进行迭代和执行操作。我们将在稍后介绍它。

除了forEach() ,这里是Maps上最常用的方法。

  • set(key_name, value) - 添加一个键值对到Map

  • get(key_name) - 返回分配给所传递的键的值,如果没有这样的键 - 返回undefined

  • has(key_name) - 返回truefalse ,取决于Map 是否有一个键key_name

console.log(myMap.has("two")); // true
console.log(myMap.has("five")) // false
  • delete(key_name) - 根据传递的key_name ,删除键和值,如果传递的是一个不存在的键--什么都不会发生。
myMap.delete("two")console.log(myMap);  
// Output: Map { 'one' => 1, 'three' => 'three' }
myMap.delete("five")console.log(myMap); 
// Output: Map { 'one' => 1, 'three' => 'three' }
  • clear() - 从Map 对象中删除每个键值对。
myMap.clear();
console.log(myMap); 
// Output: Map {}

Map 有一个主要属性--它的size 属性。它包含一个数字值,代表Map 对象的大小。

let myMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);
console.log(myMap.size); 
// Output: 2

遍历一个地图

在JavaScript中遍历一个Map 对象是有点像Python的。我们可以使用for..of 语法来实现这一点。

for (let [k, v] of myMap){	
  console.log(k + " written in number is " + v)
}

对于每个条目,用_键值_对([k, v])of myMap ,做...

one written in number is 1
two written in number is 2

或者,我们可以利用更实用的forEach() 方法。

myMap.forEach(function(value) { console.log(value);});

其结果是。

1
2
three

或者,你可以同时检索valuekey

myMap.forEach(function(value, key) { console.log(value, key);});

其结果是。

1 one
2 two
three three

Map Over Object

由于annObject 在JavaScript中也遵循键值符号,因此可能很难决定使用哪种方法以及何时使用。

关于这两者的用法,有一些提示。

  • 键值 在运行前是未知的,或者所有的键值都是同一类型,所有的值都是同一类型时,应该使用地图
  • 当有逻辑对单个元素进行操作,而不是对元素的集合进行操作时,应该使用对象

_WeakMap_对象

JavaScript中的WeakMap 对象是一个键值对的集合,其中只能是对象,值可以是各种类型。_弱的_名字来自于这些对象是垃圾收集的目标的活动--这意味着如果没有对它的引用,它将被删除。

WeakMap 的API与Map 的API相同,没有任何_迭代_方法,因为弱映射是不可迭代的。

let myMap = new WeakMap();

let athlete = class Athlete{}
myMap.set(athlete, 1);

console.log(myMap.get(athlete))

这就导致了。

1

集合对象

在JavaScript中,一个Set 对象只是一个值的集合。这些值是唯一的,这意味着不允许有重复的,试图添加一个重复的元素根本就不会添加任何东西。

我们也可以测试这一点,因为打印集合是按插入顺序打印其元素的,在开始和结束时添加一个重复的元素只会导致第一个元素的出现。

创建一个Set ,就像调用其构造函数一样简单。

let mySet = new Set();

向一个集合添加一个元素

要向一个集合添加一个新元素,我们使用add(value) 方法。

集合可以包含任意的值。让我们试着添加一些元素,并故意添加重复的元素,来看看Set 的表现如何。

mySet.add(1);
mySet.add("one");
mySet.add("one");
mySet.add("two");
mySet.add(1);
console.log(mySet);

集合保持了插入的顺序,所以我们可以很容易地测试新的1 是否覆盖了旧的1 ,或者它的添加是否被简单地跳过。

Set { 1, 'one', 'two' }

Set 识别相同的值元素,并且只保留每个元素的一个副本。集合是过滤重复值的好办法--你可以放进一堆应该是唯一的值,它们会被过滤掉。

虽然,如果你不需要最后的Set ,最好是过滤一个更合适的集合。

设置方法

设置方法与Map方法非常相似,你可以很容易地添加和删除值,以及检查一些值是否属于这个集合,或者清除它。

  • add(value) - 向Set 对象添加一个新值
  • delete(value) - 从Set 对象中删除传递的value
  • has(value) - 根据value 是否在Set 对象中,返回truefalse
  • clear() - 从Set 对象中删除所有的值。
let mySet = new Set()

// Add values
mySet.add(1);
mySet.add("two");

// Delete a value
mySet.delete("two")
// Check if the deleted value is present
console.log(mySet.has("two")) // false
// Clear all values
mySet.clear()
// Check if first value is present
console.log(mySet.has(1)) // false

WeakSet对象

一个WeakSet 对象是一个对象的集合。与Set'的值一样,WeakSet'的对象必须是_唯一的_。这指的是内存中的对象,而不是它们的字段或值。

SetWeakSet 之间有一些关键的区别。

  • WeakSet 是一个_对象的集合_,而Set 是一个任何类型的值的集合。
  • WeakMap 相同,如果没有对WeakSet 对象的引用--它将被删除。

HTML DOM集合

这种类型的集合是与前端网页开发有关的。

当在一个网页上工作时,由于_DOM树的_存在,我们可以访问页面上的所有元素。因此,当一次访问多个元素时,它们被作为一个HTMLCollection返回--一个类似数组的HTML元素集合。

如果我们有一个包含多个<p> 标签的网页,我们可以用document.getElementsByTagName("p") 来检索它们--它返回页面上所有<p> 元素的集合。

let myHTMLCollection = document.getElementsByTagName("p");
console.log(myHTMLCollection[1]);

我们现在可以认识到,HTMLCollection 是一个 "有索引 "的集合,因为我们是用一个索引值来访问其中的一个元素。它不是一个_真正的_有索引的JavaScript集合,因为它不是一个数组,因为它没有数组的方法,但是索引访问是可用的。

一个HTMLCollection ,有length 属性,它返回它的大小。

结论

根据你所处理的数据,你将决定是使用索引集合还是键控集合。如果在一个网页上工作,你可能会遇到HTMLCollections。

作为一个简单的回顾。

  • 索引型集合。
    • 元素是基于索引值的--在JavaScript中从0开始。
    • Array 对象和TypedArray 对象。
  • 有键集合。
    • 元素是基于键值对(类似JSON)。
    • Map 对象和Set 对象。
  • HTML DOM集合。
    • 元素是HTML元素,基于索引值,同样从0开始。