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

《学习JavaScript数据结构与算法(第2版)》3.2 ECMAScript 6和Stack类

关灯直达底部

我们花点时间分析一下代码,看看是否能用ECMAScript 6(ES6)的新功能来改进。

我们创建了一个可以当作类来使用的Stack函数。JavaScript函数都有构造函数,可以用来模拟类的行为。我们声明了一个私有的items变量,它只能被Stack函数/类访问。然而,这个方法为每个类的实例都创建一个items变量的副本。因此,如果要创建多个Stack实例,它就不太适合了。

看看如何用ES6新语法声明Stack类,并和前面的做法比较一下优缺点。

用ES6语法声明Stack类

首先来分析下面的代码:

class Stack {  constructor  {    this.items = ; //{1}  }  push(element){    this.items.push(element);  }  //其他方法}  

我们只是用ES6的简化语法把Stack函数转换成Stack类。这种方法不能像其他语言(Java、C++、C#)一样直接在类里面声明变量,只能在类的构造函数constructor里声明(行{1}),在类的其他函数里用this.nameofVariable就可以引用这个变量。

尽管代码看起来更简洁、更漂亮,变量items却是公共的。ES6的类是基于原型的。虽然基于原型的类比基于函数的类更节省内存,也更适合创建多个实例,却不能够声明私有属性(变量)或方法。而且,在这种情况下,我们希望Stack类的用户只能访问暴露给类的方法。否则,就有可能从栈的中间移除元素(因为我们用数组来存储其值),这不是我们希望看到的。

看看ES6语法有没有其他的方法可以创建私有属性。

  1. 用ES6的限定作用域Symbol实现类

    ES6新增了一种叫作Symbol的基本类型,它是不可变的,可以用作对象的属性。看看怎么用它来在Stack类中声明items属性:

    let _items = Symbol; //{1} class Stack {  constructor  {    this[_items] = ; //{2}  }  //Stack方法}  

    在上面的代码中,我们声明了Symbol类型的变量_items(行{1}),在类的constructor函数中初始化它的值(行{2})。要访问_items,只需把所有的this.items都换成this[_items]

    这种方法创建了一个假的私有属性,因为ES6新增的Object.getOwnPropertySymbols方法能够取到类里面声明的所有Symbols属性。下面是一个破坏Stack类的例子:

    let stack = new Stack;stack.push(5);stack.push(8);let objectSymbols = Object.getOwnPropertySymbols(stack);console.log(objectSymbols.length); // 1console.log(objectSymbols); // [Symbol]console.log(objectSymbols[0]); // Symbolstack[objectSymbols[0]].push(1);stack.print; //输出 5, 8, 1  

    从以上代码可以看到,访问stack[objectSymbols[0]]是可以得到_items的。并且,_items属性是一个数组,可以进行任意的数组操作,比如从中间删除或添加元素。我们操作的是栈,不应该出现这种行为。

    还有第三个方案。

  2. 用ES6的WeakMap实现类

    有一种数据类型可以确保属性是私有的,这就是WeakMap。我们会在第7章深入探讨Map这种数据结构,现在只需要知道WeakMap可以存储键值对,其中键是对象,值可以是任意数据类型。

    如果用WeakMap来存储items变量,Stack类就是这样的:

    const items = new WeakMap; //{1} class Stack {  constructor  {    items.set(this, ); //{2}  }  push(element) {    let s = items.get(this); //{3}    s.push(element);  }  pop {    let s = items.get(this);    let r = s.pop;    return r;  }  //其他方法}  
    • {1},声明一个WeakMap类型的变量items

    • {2},在constructor中,以thisStack类自己的引用)为键,把代表栈的数组存入items

    • {3},从WeakMap中取出值,即以this为键(行{2}设置的)从items中取值。

    现在我们知道,itemsStack类里是真正的私有属性了,但还有一件事要做。items现在仍然是在Stack类以外声明的,因此谁都可以改动它。我们要用一个闭包(外层函数)把Stack类包起来,这样就只能在这个函数里访问WeakMap

    let Stack = (function  {  const items = new WeakMap;  class Stack {     constructor  {       items.set(this, );     }     //其他方法  }  return Stack; //{5}}); 

    Stack函数里的构造函数被调用时,会返回Stack类的一个实例(行{5})。

     关于JavaScript闭包,请阅读http://www.w3schools.com/js/js_function_closures.asp。

    现在,Stack类有一个名为items的私有属性。虽然它很丑陋,但毕竟实现了私有属性。然而,用这种方法的话,扩展类无法继承私有属性。鱼与熊掌不可兼得!

    把上面的代码跟本章最初实现的Stack类做个比较,我们会发现有一些相似之处:

    function Stack {  let items = ;  //其他方法}  

    事实上,尽管ES6引入了类的语法,我们仍然不能像在其他编程语言中一样声明私有属性或方法。有很多种方法都可以达到相同的效果,但无论是语法还是性能,这些方法都有各自的优点和缺点。

    哪种方法更好?这取决于你在实际项目中如何使用本书中这些算法,要处理的数据量,要创建的实例个数,以及其他约束条件。最终,还是取决于你。

     在本书提供下载的代码中,所有的数据结构都会包含简单的函数类,以及ES6的WeakMap和闭包的创建方法。