集合Set、Map

9.1 数组

数组是最常见的数据类型之一。使用数组,可以处理数据集合。使用数组不好的副作用是性能方面;但好的方面是数组可以访问方法,与其他对象方法一样,使用起来更容易。

9.1.1 创建数组

创建数组有两种基本方式:

  • 使用内置Array构造函数
  • 使用数组字面量[]
const ninjas = ["Kuma", "Hattori", "Yagyu"];
const samurai = new Array("Oda", "Tomoe");

assert(ninjas.length === 3, "There are three ninjas");
assert(samurai.length === 2, "And only two samurai");

assert(ninjas[0] === "Kuma", "Kuma is the first ninja");
assert(samurai[samurai.length-1] === "Tomoe", "Tomoe is the last samurai");
assert(ninjas[4] === undefined, "We get undefined if we try to access an out of bounds index");

ninjas[4] = "Ishi";
assert(ninjas.length === 5,  "Arrays are automatically expanded");

ninjas.length = 2;
assert(ninjas.length === 2, "There are only two ninjas now");
assert(ninjas[0] === "Kuma" && ninjas[1] === "Hattori", "Kuma and Hattori");
assert(ninjas[2] === undefined, "But we’ve lost Yagyu");
  • 创建两个数组,通过数组字面量创建数组ninjas,数组ninjas中立即填充了3个元素。
  • 数组samurai通过内置的Array构造函数创建。
  • 数组字面量与数组构造函数
    • 使用数组字面量创建数组优于数组构造函数。原因很简单:[]与new Array()。此外,由于JavaScript的高度动态特性,无法阻止修改内置的Array构造函数,也就意味着new Array()创建的不一定是数组。因此推荐使用数组字面量创建数组。
  • 无论使用哪种方式创建数组,每个数组都具有length属性,表示数组的长度。例如ninjas的长度是3。
  • 通过使用索引访问数组元素,第一个元素的索引是0,最后一个元素的索引是数组长度减1。但是,如果试图访问数组长度之外的索引,它不会抛出异常,而是返回undefined。
  • 这表明,JavaScript的数组是对象,假如访问不存在的对象,会返回undefined。访问不存在的数组索引,也会返回undefined。
  • 如果在数组边界之外写入元素,则数组会扩大以适应新的形势,如:ninjas[4] = “Ishi”;。实际上在数组中创建了一个空元素,索引为3的元素为undefined。同时改变了length属性,虽然只有一个元素是undefined,但现在数组长度是5.
  • JavaScript在length属性上,也表现出一种特殊功能:可以手动修改length属性的值,将length值改为比原有值大的数,数组会被拓展,新拓展出的元素均为undefined;将length值改为比原有值小的数,数组会被裁减。

9.1.2 在数组两端添加、删除元素

为数组添加、删除元素的方法:

  • push:在数组末尾添加元素
  • unshift:在数组开头添加元素
  • pop:在数组末尾删除元素
  • shift:在数组开头删除元素
const ninjas = [];
assert(ninjas.length === 0, "An array starts empty");

ninjas.push("Kuma");
assert(ninjas[0] === "Kuma", "Kuma is the first item in the array");
assert(ninjas.length === 1, "We have one item in the array");

ninjas.push("Hattori");
assert(ninjas[0] === "Kuma", "Kuma is still first");
assert(ninjas[1] === "Hattori", "Hattori is added to the end of the array");
assert(ninjas.length === 2, "We have two items in the array!");

ninjas.unshift("Yagyu");
assert(ninjas[0] === "Yagyu", "Now Yagyu is the first item");
assert(ninjas[1] === "Kuma", "Kuma moved to the second place");
assert(ninjas[2] === "Hattori", "And Hattori to the third place");
assert(ninjas.length === 3, "We have three items in the array!");

const lastNinja = ninjas.pop();
assert(lastNinja === "Hattori", "We've removed Hattori from the end of the array");
assert(ninjas[0] === "Yagyu", "Now Yagyu is still the first item");
assert(ninjas[1] === "Kuma", "Kuma is still on the second place");
assert(ninjas.length === 2, "Now there are two items in the array");

const firstNinja = ninjas.shift();
assert(firstNinja === "Yagyu", "We've removed Yagyu from the beginning of the array");
assert(ninjas[0] === "Kuma", "Kuma has shifted to the first place");
assert(ninjas.length === 1, "There's only one ninja in the array");
  • 首先创建一个空数组,在每个数组中,可以使用数组内置的push方法在数组末尾添加元素,同时改变数组的长度。使用内置的unshift方法可以在数组开头添加元素;还可以分别从数组起始位置和结束位置删除元素,调用pop方法从数组末尾删除元素,减小数组长度,使用shift方法从数组起始位置删除元素。
  • 性能考虑:pop和push与shift和unshift
    • pop和push方法只影响最后一个元素:pop移除最后一个元素,push在数组末尾增加元素。shift和unshift方法修改第一个元素,之后的每一个元素的索引都需要调整。因此,pop和push方法比shift和unshift要快得多,非特殊情况下不建议使用shift和unshift方法。

9.1.3 在数组任意位置添加、删除元素

// 删除数组元素
const ninjas = ["Yagyu", "Kumawakamaru", "Hattori", "Fuma"];

delete ninjas[1];

assert(ninjas.length === 4, "Length still reports that there are 4 items");

assert(ninjas[0] === "Yagyu", "First item is Yagyu");
assert(ninjas[1] === undefined, "We've simply created a hole");
assert(ninjas[2] === "Hattori", "Hattori is still the third item");
assert(ninjas[3] === "Fuma", "And Fuma is the last item");
  • 这种删除数组元素的方法无效,只是在数组中创建了一个空元

JavaScript中所有的数组都有splice方法:从给出的索引开始,splice可以完成删除、插入元素。

// 在数组任意位置删除、添加元素
const ninjas = ["Yagyu", "Kumawakamaru", "Hattori", "Fuma"];

var removedItems = ninjas.splice(1, 1);

assert(removedItems.length === 1, "One item was removed");
assert(removedItems[0] === "Kumawakamaru");

assert(ninjas.length === 3, "There are now three items in the array");
assert(ninjas[0] === "Yagyu", "The first item is still Yagyu");
assert(ninjas[1] === "Hattori", "Hattori is now in the second place");
assert(ninjas[2] === "Fuma", "And Fuma is in the third place");

removedItems = ninjas.splice(1, 2, "Mochizuki", "Yoshi", "Momochi");
assert(removedItems.length === 2, "Now, we've removed two items");
assert(removedItems[0] === "Hattori", "Hattori was removed");
assert(removedItems[1] === "Fuma", "Fuma was removed");

assert(ninjas.length === 4, "We've inserted some new items");
assert(ninjas[0] === "Yagyu", "Yagyu is still here");
assert(ninjas[1] === "Mochizuki", "Mochizuki also");
assert(ninjas[2] === "Yoshi", "Yoshi also");
assert(ninjas[3] === "Momochi", "and Momochi");
  • 首先创建一个具有4个元素的数组,然后调用splice方法。
  • splice方法具有两个参数:起始索引位置和需要移除元素的个数(这个参数如果不传,会一直删除元素直到数组末尾的元素)。本例中,索引是1的元素被删除,后续元素自动进行相应的移动。
  • 同时,splice方法返回被移除的元素数组。
  • 使用splice方法,也可以实现数组任意位置插入元素,本例中从索引1开始,首先移除2个元素,然后添加三个元素。

9.1.4 数组常用操作

数组最常用的操作:
  • 遍历数组
  • 基于现有的数组元素映射创建新数组
  • 验证数组元素是否匹配指定的条件
  • 查找特定数组元素
  • 聚合数组,基于数组元素计算
遍历数组
  • 数组遍历是最常用的操作之一,常用for循环查询数组中的每个元素。为了遍历数组,创建变量i,指向数组的长度,定义计数器的修改。这样有很多相同的操作,导致阅读困难。
  • 为了简化,可以使用JavaScript内置的forEach方法。
const ninjas = ["Yagyu", "Kuma", "Hattori"];

ninjas.forEach(ninja => {
	assert(ninja !== null, ninja);
});
  • 提供回调函数,遍历每一个元素时立即执行。不需要考虑起始索引、结束条件和计数器。JavaScript引擎解决了全部问题,这段代码更容易理解,并且会减小缺陷的发生。
映射数组
  • 假设有一个数组对象ninja。每个ninja具有name和weapon属性,需要从这个数组中提取全部的weapon。使用forEach方法,可以编写如下方法。
const ninjas = [
		{name: "Yagyu", weapon: "shuriken"},
		{name: "Yoshi", weapon: "katana"},
		{name: "Kuma", weapon: "wakizashi"}
];

const weapons = [];
ninjas.forEach(ninja => {
		weapons.push(ninja.weapon);
});

assert(weapons[0] === "shuriken"
    && weapons[1] === "katana"
    && weapons[2] === "wakizashi"
    && weapons.length === 3, "The new array contains all weapons");
  • 首先创建一个空数组,使用forEach方法遍历ninjas数组,然后,将每个ninja对象的weapon属性添加到weapons数组中。
  • 可以想象,基于已有数组的元素创建数组是非常常见的,因此它具有一个特殊的名称:映射数组。主要思想是将数组中的每个元素的属性映射到新数组的元素上。JavaScript的map函数可以实现便捷操作。
// 映射数组
const ninjas = [
		{name: "Yagyu", weapon: "shuriken"},
		{name: "Yoshi", weapon: "katana"},
		{name: "Kuma", weapon: "wakizashi"}
];

const weapons = ninjas.map(ninja => ninja.weapon);

assert(weapons[0] === "shuriken" 
    && weapons[1] === "katana"
    && weapons[2] === "wakizashi" 
    && weapons.length === 3, "The new array contains all weapons");
  • 内置的map方法创建了一个全新的数组,然后遍历输入的数组。对输入数组的每个元素,在新建的数组上,都会基于回调函数的执行结果创建一个对应的元素。
测试数组元素
  • 处理集合的元素时,常常遇到需要知道数组的全部元素或部分元素是否满足某些条件。JavaScript数组具有内置的every和some方法。
// 使用every和some方法测试数组
const ninjas = [
	{name: "Yagyu", weapon: "shuriken"},
    {name: "Yoshi" },
    {name: "Kuma", weapon: "wakizashi"}
];

const allNinjasAreNamed = ninjas.every(ninja => "name" in ninja);
const allNinjasAreArmed = ninjas.every(ninja => "weapon" in ninja);

assert(allNinjasAreNamed, "Every ninja has a name");
assert(!allNinjasAreArmed, "But not every ninja is armed");

const someNinjasAreArmed = ninjas.some(ninja => "weapon" in ninja);

assert(someNinjasAreArmed, "But some ninjas are armed");
  • every方法接收回调函数,对集合中的每个ninja对象检查是否含有name属性。当且仅当全部的回调函数都返回true时,every方法才返回true,否则返回false。
  • 当只关心数组中的部分元素是否满足条件时,可以使用some方法。some方法从数组的第一项开始执行回调函数,直到回调函数返回true。如果有一项元素执行回调函数时,返回true,some方法返回true,否则,some方法返回false。
数组查找
  • 在数组中查找指定元素。通过内置方法find可以简化任务。
const ninjas = [
  {name: "Yagyu", weapon: "shuriken"},
    {name: "Yoshi" },
    {name: "Kuma", weapon: "wakizashi"}
];

const ninjaWithWakizashi = ninjas.find(ninja => ninja.weapon === "wakizashi");

assert(ninjaWithWakizashi.name === "Kuma" && ninjaWithWakizashi.weapon === "wakizashi",  "Kuma is wielding a wakizashi");

const ninjaWithKatana = ninjas.find(ninja => ninja.weapon === "katana");

assert(ninjaWithKatana === undefined,  "We couldn’t find a ninja that wields a katana");

const armedNinjas = ninjas.filter(ninja => "weapon" in ninja);

assert(armedNinjas.length === 2, "There are two armed ninjas:");
assert(armedNinjas[0].name === "Yagyu" && armedNinjas[1].name === "Kuma", "Yagyu and Kuma");
  • 查找满足一定条件的数组元素很容易,使用内置的find方法,传入回调函数,针对集合中的每个元素调用回调函数,直到查找到目标元素。由回调函数返回true。
  • 如果数组中没有一项返回true的元素,则查找结果是undefined。
  • 如果需要查找多个满足条件的元素,可以使用filter方法,该方法返回满足所有条件的多个元素的数组。
获取元素的索引
const ninjas = ["Yagyu", "Yoshi", "Kuma", "Yoshi"];

assert(ninjas.indexOf("Yoshi") === 1, "Yoshi is at index 1");
assert(ninjas.lastIndexOf("Yoshi") === 3, "and at index 3");

const yoshiIndex = ninjas.findIndex(ninja => ninja === "Yoshi");

assert(yoshiIndex === 1, "Yoshi is still at index 1");
  • 使用内置的indexOf方法查找特定元素的索引,传入目标元素作为参数
  • 有时在数组中具有多个指定的元素,查找最后一次出现的索引可以使用lastIndexOf方法
  • 当不具有目标元素的引用时,可以使用findIndex方法查找索引,该方法接收回调函数,并返回第一个回调函数返回true的元素。find方法返回元素本身,findIndex方法返回元素的索引。
数组排序
  • sort方法
  • JavaScript引擎实现了排序算法,需要提供回调函数,告诉排序算法相邻的两个元素的关系。
    • 如果回调函数的返回值小于0,元素a应该出现在元素b之前
    • 如果回调函数的返回值等于0,元素a和元素b应该出现在相同位置
    • 如果回调函数的返回值大于0,元素a应该出现在元素b之后
合计数组元素
  • 使用reduce合计数组元素
const numbers = [1, 2, 3, 4];

// 合计值与当前元素
const sum = numbers.reduce((aggregated, number) => 
aggregated + number, 0);

assert(sum === 10, "The sum of first four numbers is 10")

9.1.5 复用内置的数组函数

在对象上添加属性和方法,包括数组,这种方法效率低且单调乏味。
  • 使用简单对象,加上需要的方法。
		    const elems = {
		      length: 0,
		      add: function(elem){
		        Array.prototype.push.call(this, elem);
		      },
		      gather: function(id){
		        this.add(document.getElementById(id));
		      },
		      find: function(callback){
		        return Array.prototype.find.call(this, callback);
		      }
		    };

		    elems.gather("first");
		    assert(elems.length === 1 && elems[0].nodeType,
		           "Verify that we have an element in our stash");

		    elems.gather("second");
		    assert(elems.length === 2 && elems[1].nodeType,
		           "Verify the other insertion");

		    const found = elems.find(elem => elem.id === "second");
		    assert(found && found.id === "second",
		           "We’ve found our element");
  • 本例创建对象,并模拟一些数组的行为,首先定义length属性用于存储元素的数量,与数组类似。然后定义在末尾添加元素的add方法。
  • 复用JavaScript数组的方法:Array.prototype.push。
  • 通常,该方法通过自身函数上下文执行数组。但是,通过使用call方法,将上下文改为定义的对象。push方法增加length属性,为所添加的元素增加编号,通过这种方式,该对象的行为是颠覆性的,也说明可变对象上下文的用途。
  • add方法接收一个待添加到对象中的元素作为参数。有时可能没有类似的元素,因此,定义了一个gather方法,该方法可以通过ID查找元素并添加到对象中。
  • 最后,利用内置的数组方法find实现自定义对象的find方法,用于查找自定义对象中的任意元素。

9.2 Map

将key映射到指定的值上,在不同的编程语言中具有不同的名称,通常称为字典或Map。

在JavaScript中利用对象是属性名与属性值的特性,创建字典。但是,通常来说,这种方法并不可靠。

9.2.1 别把对象当作Map

// 访问对象未显式定义的属性
    		const dictionary = {
          "ja": {
            "Ninjas for hire": "レンタル用の忍者"
          },
          "zh": {
            "Ninjas for hire": "忍者出租"
          },
          "ko": {
            "Ninjas for hire":"고용 닌자"
             }
        };

        assert(dictionary.ja["Ninjas for hire"] === "レンタル用の忍者", "We know how to say 'Ninjas for hire' in Japanese!");
        assert(typeof dictionary.ja["constructor"] === "undefined", dictionary.ja["constructor"]);
  • 试图访问constructor属性,这是在字典中未定义的单词。本例中期望字典返回undefined,但结果并不是这样。
  • 访问constructor属性,返回如下字符串function Object() { [native code] }
  • 由于每个对象都有原型,尽管定义新的空对象作为map,仍然可以访问原型对象的属性。原型对象的属性之一是constructor,它指回构造函数本身,它是造成混乱的罪魁祸首。
  • 同时,对象的key必须是字符串,如果想映射为其他类型,它会默默转化为字符串,没有任何提示。
// 将对象的key映射为HTML节点
		<div id="firstElement"></div>
  		<div id="secondElement"></div>
  		<script>
    		const firstElement = document.getElementById("firstElement");
    		const secondElement = document.getElementById("secondElement");

    		const map = {};

    		map[firstElement] = { data: "firstElement"};
    		assert(map[firstElement].data === "firstElement", "The first element is correctly mapped");

    		map[secondElement] = { data: "secondElement"};
    		assert(map[secondElement].data === "secondElement", "The second element is correctly mapped");

    		assert(map[firstElement].data === "firstElement", "But now the firstElement is overriden!");
  • 在这个例子中,创建了两个HTML元素:firstElement和secondElement,通过document.getElementById方法从DOM中获取到这两个元素。为了存储每个元素的更多信息,定义了一个JavaScript对象。
  • 然后使用HTML元素作为对象的key,存储一些数据
  • 然后检查数据,对第二个元素执行相同的过程
  • 看起来一切都很完美,成功的将数据关联到HTML
    元素上,但事实并非如此。
  • 这是因为对象的key必须是字符串,这意味着当试图使用非字符串类型的元素作为key时,其值被toString方法静默转换为字符串类型。HTML元素转换为字符串后的值为[object HTMLDivElement], 第一个元素的数据信息被存储在[object HTMLDivElement]属性中。
  • 接着,试图为第二个元素创建映射时,发生了相同的过程。第二个元素也是HTML元素,也被转换成字符串,对应的数据也被存储在[object HTMLDivElement]属性上,覆盖了第一个元素的值。
  • 由于这两个原因:原型继承属性以及key仅支持字符串,所以通常不能使用对象作为map。由于这种限制,ECMAScript委员会定义了一个全新类型Map。

9.2.2 创建map

创建map只需要使用内置的构造函数Map。

// 创建一个map
			const ninjaIslandMap = new Map();

  			const ninja1 = { name: "Yoshi"};
  			const ninja2 = { name: "Hatori"};
  			const ninja3 = { name: "Kuma"};

  			ninjaIslandMap.set(ninja1, { homeIsland: "Honshu"});
  			ninjaIslandMap.set(ninja2, { homeIsland: "Hokkaido"});

  			assert(ninjaIslandMap.get(ninja1).homeIsland === "Honshu", "The first mapping works");
  			assert(ninjaIslandMap.get(ninja2).homeIsland === "Hokkaido", "The second mapping works");

  			assert(ninjaIslandMap.get(ninja3) === undefined, "There is no mapping for the third ninja!");

  			assert(ninjaIslandMap.size === 2, "We’ve created two mappings");

  			assert(ninjaIslandMap.has(ninja1) && ninjaIslandMap.has(ninja2),  "We have mappings for the first two ninjas");
  			assert(!ninjaIslandMap.has(ninja3),  "But not for the third ninja!");

  			ninjaIslandMap.delete(ninja1);
			assert(!ninjaIslandMap.has(ninja1) && ninjaIslandMap.size === 1,  "There’s no first ninja anymore!");

			ninjaIslandMap.clear();
			assert(ninjaIslandMap.size === 0,  "All mappings have been cleared");
  • 调用Map构造函数创建map,然后创建三个对象,分别命名ninja1、ninja2、ninja3.使用set方法,然后通过get 方法获取前两个ninja对象的映射
  • 只有前两个ninja对象存在映射,第三个对象不存在映射,因为第三个对象没有被set调用。
  • 除了get和set方法之外,map还具有size属性以及has、delete方法。
  • size属性告诉我们已经创建了多少映射。
  • has方法用于判断指定的key是否存在
  • delete方法可用于删除映射
  • 处理map时的一个基本概念是确定两个映射的key是否相等。
  • map是键值对的集合,key可以是任意类型的值,甚至可以是对象。
key相等
// key相等
			const map = new Map();
			const currentLocation = location.href;

			const firstLink = new URL(currentLocation);
			const secondLink = new URL(currentLocation);

			map.set(firstLink, { description: "firstLink"});
			map.set(secondLink, { description: "secondLink"});

			assert(map.get(firstLink).description === "firstLink",  "First link mapping" );
			assert(map.get(secondLink).description === "secondLink", "Second link mapping");
			assert(map.size === 2, "There are two mappings");
  • 使用location.href属性获取当前页面的URL,然后,使用URL构造函数创建两个URL当前页面链接的对象。接着对每个链接对象关联描述信息。最后,检查映射是否正确创建。
  • 在JavaScript中,不能重载相等运算符,虽然两个对象的内容相同,但是两个对象仍然不相等。

9.2.3 遍历map

map的优点:

  • 可以确定map中只存在你放入的内容,可以使用任意类型的数据作为key等。
  • 因为map是集合,可以使用for…of循环遍历map。也可以确保遍历的顺序与插入的顺序一致(在对象上使用for…of循环无法保证)
// 遍历map
			const directory = new Map();

			directory.set("Yoshi", "+81 26 6462");
			directory.set("Kuma", "+81 52 2378 6462");
			directory.set("Hiro", "+81 76 277 46");

			for(let item of directory){
  				assert(item[0] !== null, "Key:" + item[0]);
  				assert(item[1] !== null, "Value:" + item[1]);
			}

			for(let key of directory.keys()){
  				assert(key !== null, "Key:" + key);
  				assert(directory.get(key) !== null, "Value:" + directory.get(key));
			}

			for(let value of directory.values()){
  				assert(value !== null, "Value:" + value);
  • 在每个迭代中,每个元素是具有两个值的数组,第一个值是key,第二个值是value。也可以分别使用keys和values方法遍历key和value。

9.3 Set

Set集合中的元素时唯一的。

// 通过对象模拟Set
			function Set(){
  				this.data = {};
  				this.length = 0;
			}

			Set.prototype.has = function(item){
  				return typeof this.data[item] !== "undefined";
   			}

			Set.prototype.add = function(item){
  				if(!this.has(item)){
    				this.data[item] = true;
    				this.length++;
  				}
			};

			Set.prototype.remove = function(item){
			  if(this.has(item)){
			    delete this.data[item];
			    this.length--;
			  }
			}

			const ninjas = new Set();
			ninjas.add("Hattori");
			ninjas.add("Hattori");

			assert(ninjas.has("Hattori") && ninjas.length === 1,  "Our set contains only one Hattori");

			ninjas.remove("Hattori");
			assert(!ninjas.has("Hattori") && ninjas.length === 0,  "Our set is now empty");
  • 使用对象存储数据,持续跟踪集合中的元素,提供三个方法:has-验证集合中是否存在元素;add-如果集合中不存在元素则将元素添加到集合中;remove-删除集合中已存在的元素。
  • 这仅仅是模拟,因为map不能真正的存储对象,只能存储字符串或数字,仍然存在访问原型对象的风险。

9.3.1 创建Set

创建Set的方法是使用构造函数:Set。

			const ninjas = new Set(["Kuma", "Hattori", "Yagyu", "Hattori"]);

			assert(ninjas.has("Hattori"), "Hattori is in our set");
			assert(ninjas.size === 3, "There are only three ninjas in our set!");

			assert(!ninjas.has("Yoshi"), "Yoshi is not in, yet..");
			ninjas.add("Yoshi");
			assert(ninjas.has("Yoshi"), "Yoshi is added");
			assert(ninjas.size === 4, "There are four ninjas in our set!");

			assert(ninjas.has("Kuma"), "Kuma is already added");
			ninjas.add("Kuma");
			assert(ninjas.size === 4, "Adding Kuma again has no effect");

			for(let ninja of ninjas) {
  				assert(ninja, ninja);
			}
  • 使用内置构造函数创建Set。如果不传入任何参数,将创建一个空Set。可以传入一个数组,用来预填充Set:["Kuma", "Hattori", "Yagyu", "Hattori"]

  • Set的成员值都是唯一的,最重要的作用是避免存储多个相同的对象。

  • Set具有多个可访问的方法。

    • 例如has方法验证Set中是否存在元素。
    • add方法用于添加唯一成员
    • size属性可以查看Set有几个属性
  • 与Map和数组类似,Set也是集合,可以使用for-of循环进行遍历。

9.3.2 并集

  • 两个集合的合并指的是创建一个新的集合,同时包含A和B中的所有成员。在新的集合中的元素也不允许出现两次。
			const ninjas = ["Kuma", "Hattori", "Yagyu"];
			const samurai = ["Hattori", "Oda", "Tomoe"];

			const warriors = new Set([...ninjas, ...samurai]);

			assert(warriors.has("Kuma"), "Kuma is here");
			assert(warriors.has("Hattori"), "And Hattori");
			assert(warriors.has("Yagyu"), "And Yagyu");
			assert(warriors.has("Oda"), "And Oda");
			assert(warriors.has("Tomoe"), "Tomoe, last but not least");

			assert(warriors.size === 5, "There are 5 warriors in total");
  • 首先创建两个数组ninjas与samurai,两个数组中都具有Hattori元素。假设同时需要两个数组中的元素,因此,创建一个新的Set,同时包含ninjas和samurai两个数组中的元素。虽然两个数组都含有Hattori元素,但是希望在集合中只含有一个Hattori元素。
  • 在本例中,使用Set是最完美的。不需要手动判断元素是否已存在:Set会自动处理。当创建新Set时,使用延展运算符创建包含两个数组的Set。当数组传入Set构造函数时,Set中只含有一个Hattori元素。

9.3.3 交集

  • 两个集合的交集指的是创建新集合,该集合中只包含集合A与B中同时出现的成员。
  • 例如可以看见同时在ninjas和samurai中出现的元素。
			const ninjas = new Set(["Kuma", "Hattori", "Yagyu"]);
			const samurai = new Set(["Hattori", "Oda", "Tomoe"]);

			const ninjaSamurais = new Set(
			  [...ninjas].filter(ninja => samurai.has(ninja))
			);

			assert(ninjaSamurais.size === 1, "There’s only one ninja samurai");
			assert(ninjaSamurais.has("Hattori"), "Hattori is his name");
  • 创建一个新集合,该集合只包含ninjas数组与samurai数组中同时存在的元素。
  • 通过使用数组的filter方法,使得数组中的元素都符合回调函数中指定的特定条件。
  • 本例中,指定的条件是元素在ninjas数组与sumurai数组中同时存在。filter只能操作数组,使用延展运算符将Set转换为数组。

9.3.4 差集

  • 两个集合的差集指的是创建新集合,只包含存在于集合A,但不存在于集合B中的元素。
			const ninjas = new Set(["Kuma", "Hattori", "Yagyu"]);
			const samurai = new Set(["Hattori", "Oda", "Tomoe"]);

			const pureNinjas = new Set(
			  [...ninjas].filter(ninja => !samurai.has(ninja))
			);

			assert(pureNinjas.size === 2, "There’s only one ninja samurai");
			assert(pureNinjas.has("Kuma"), "Kuma is a true ninja");
			assert(pureNinjas.has("Yagyu"), "Yagyu is a true ninja");
  • 在表达式samurai.has(ninja)前添加一个! 就得到了差集。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值