首页 » 学习JavaScript数据结构与算法(第2版) » 学习JavaScript数据结构与算法(第2版)全文在线阅读

《学习JavaScript数据结构与算法(第2版)》6.2 创建集合

关灯直达底部

目前的JavaScript实现是基于2011年6月发布的ECMAScript 5.1(现代浏览器均已支持),它包括了我们在之前章节已经提到过的Array类的实现。ECMAScript 6也包括了Set类的实现,本章稍后会介绍它的用法。本章中,我们要实现的类就是以ECMAScript 6中Set类的实现为基础的。

以下是我们的Set类的骨架:

function Set {  let items = {};}  

有一个非常重要的细节,我们使用对象而不是数组来表示集合(items)。但也可以用数组实现。在这里我们用对象来实现,稍微有点儿不一样,也学习一下实现相似数据结构的新方法。同时,JavaScript的对象不允许一个键指向两个不同的属性,也保证了集合里的元素都是唯一的。

接下来,需要声明一些集合可用的方法(我们会尝试模拟与ECMAScript 6实现相同的Set类)。

  • add(value):向集合添加一个新的项。

  • delete(value):从集合移除一个值。

  • has(value):如果值在集合中,返回true,否则返回false

  • clear:移除集合中的所有项。

  • size:返回集合所包含元素的数量。与数组的length属性类似。

  • values:返回一个包含集合中所有值的数组。

6.2.1 has(value)方法

首先要实现的是has(value)方法。这是因为它会被addremove等其他方法调用。下面看看它的实现:

this.has = function(value){  return value in items;};  

既然我们使用对象来存储集合的值,就可以用JavaScript的in操作符来验证给定的值是否是items对象的属性。

但这个方法还有更好的实现方式,如下:

this.has = function(value){  return items.hasOwnProperty(value);};  

所有JavaScript对象都有hasOwnProperty方法。这个方法返回一个表明对象是否具有特定属性的布尔值。

6.2.2 add方法

接下来要实现add方法:

this.add = function(value){  if (!this.has(value)){    items[value] = value; //{1}    return true;  }  return false;};  

对于给定的value,可以检查它是否存在于集合中。如果不存在,就把value添加到集合中(行{1}),返回true,表示添加了这个值。如果集合中已经有这个值,就返回false,表示没有添加它。

 添加一个值的时候,把它同时作为键和值保存,因为这样有利于查找这个值。

6.2.3 removeclear方法

下面要实现remove方法:

this.remove = function(value){  if (this.has(value)){    delete items[value]; //{2}    return true;  }  return false;};  

remove方法中,我们会验证给定的value是否存在于集合中。如果存在,就从集合中移除value(行{2}),返回true,表示值被移除;否则返回false

既然用对象来存储集合的items对象,就可以简单地使用delete操作符从items对象中移除属性(行{2})。

使用Set类的示例代码如下:

let set = new Set;set.add(1);set.add(2);  

出于好奇,如果在执行以上代码之后,在控制台(console.log)输出items变量,谷歌Chrome就会输出如下内容:

Object {1: 1, 2: 2}  

 可以看到,这是一个有两个属性的对象。属性名就是添加到集合的值,同时它也是属性值。

如果想移除集合中的所有值,可以用clear方法:

this.clear = function{  items = {}; // {3}};  

要重置items对象,需要做的只是把一个空对象重新赋值给它(行{3})。我们也可以迭代集合,用remove方法依次移除所有的值,但既然有更简单的方法,那样做就太麻烦了。

6.2.4 size方法

下一个要实现的是size方法(返回集合中有多少项)。这个方法有三种实现方式。

第一种方法是使用一个length 变量,每当使用addremove方法时控制它,就像在上一章中使用LinkedList类一样。

第二种方法,使用JavaScript内建的Object类的一个内建函数(ECMAScript 5以上版本):

this.size = function{  return Object.keys(items).length; //{4}};  

JavaScript的Object类有一个keys方法,它返回一个包含给定对象所有属性的数组。在这种情况下,可以使用这个数组的length属性(行{4})来返回items对象的属性个数。以上代码只能在现代浏览器中运行(比如IE9以上版本、Firefox 4以上版本、Chrome 5以上版本、Opera 12以上版本、Safari 5以上版本,等等)。

第三种方法是手动提取items对象的每一个属性,记录属性的个数并返回这个数字。这个方法可以在任何浏览器上运行,和之前的代码是等价的:

this.sizeLegacy = function{  let count = 0;  for(let key in items) { //{5}    if(items.hasOwnProperty(key)) //{6}    ++count; //{7}  }  return count;};  

遍历items对象的所有属性(行{5}),检查它们是否是对象自身的属性(避免重复计数——行{6})。如果是,就递增count变量的值(行{7}),最后在方法结束时返回这个数字。

 不能简单地使用for-in语句遍历items对象的属性,并递增count变量的值。还需要使用hasOwnProperty方法(以验证items对象具有该属性),因为对象的原型包含了额外的属性(属性既有继承自JavaScript的Object类的,也有属于对象自身,未用于数据结构的)。

6.2.5 values方法

values方法也应用了相同的逻辑,提取items对象的所有属性,以数组的形式返回:

this.values = function{  let values = ;  for (let i=0, keys=Object.keys(items); i<keys.length; i++) {    values.push(items[keys[i]]);  }  return values;};  

以上代码只能在现代浏览器中运行。在本书中我们使用的测试浏览器是Chrome和Firefox,因此代码可以工作。

如果想让代码在任何浏览器中都能执行,可以用与之前代码等价的下面这段代码:

this.valuesLegacy = function{  let values = ;  for(let key in items) { //{7}    if(items.hasOwnProperty(key)) { //{8}      values.push(items[key]);    }  }  return values;};  

所以,首先遍历items对象的所有属性(行{7}),把它们添加一个数组中(行{8}),并返回这个数组。该方法类似于我们开发的sizeLegacy方法,但我们添加一个数组,而不是计算属性个数。

6.2.6 使用Set

现在数据结构已经完成了,看看如何使用它吧。试着执行一些命令,测试我们的Set类:

let set = new Set;set.add(1);console.log(set.values); //输出[/"1/"]console.log(set.has(1)); //输出trueconsole.log(set.size); //输出1set.add(2);console.log(set.values); //输出[/"1/", /"2/"]console.log(set.has(2)); //trueconsole.log(set.size); //2set.remove(1);console.log(set.values); //输出[/"2/"]set.remove(2);console.log(set.values); //输出  

现在我们有了一个和ECMAScript 6中非常类似的Set类实现。如前所述,也可以用数组替代对象,存储元素。既然我们在第2章、第3章和第4章都用过数组,知道有不同方式实现同样的东西,这也不错。