用js来实现那些数据结构04(栈01-栈的实现)

  其实说到底,在js中栈更像是一种变种的数组,只是没有数组那么多的方法,也没有数组那么灵活。可是栈和队列这两种数据结构比数组更加的高效和可控。而在js中要想模拟栈,依据的主要形式也是数组。html

  从这篇文章开始,可能会接触到一些原型,原型链,类,构造函数等相关的js概念,可是这里并不会过多的介绍这些概念,必要的时候会进行一些简要的说明,推荐你们去看看汤姆大叔的深刻理解Javascript系列,王福朋大神的深刻理解Javascript原型和闭包系列。都是极为不错的深度好文,推荐你们能够深刻学习。es6

  要想实现一个数据结构,首先你要明白它的基本原理,那么栈是什么?又是如何工做的呢?数组

  栈(stack)是一种遵循后进先出(Last In First Out)原则的有序集合。新添加的元素和待删除的元素都保存在栈的同一端,称为栈顶,另外一端就叫作栈底。在栈里,新元素都接近栈顶,旧元素都靠近栈底。其实能够把栈简单理解成往一个木桶里堆叠的放入物品,最后放进去的在桶的顶端,也是能够最早拿出来的,而最早放进去的却在桶的底部,只有把全部上面的物品拿出来以后才能够拿走底部的物品。数据结构

  对于数组来讲,能够添加元素,删除元素,获取数组的长度以及返回对应下标获得值,那么在开始构造一个栈以前,咱们须要了解一下栈都有哪些基本操做。闭包

  一、压栈,也称之为入栈,也就是把元素加入栈中。就像是数组中的push同样。函数

  二、出栈,移除栈顶的元素。就像是数组中的pop同样。学习

  三、获取栈顶的元素,不对栈作任何其余操做。就像是在数组中经过下标获取对应的值同样。测试

  四、判断栈是否为空。就像是判断数组的长度是否为0同样。this

  五、清空栈,也就是移除栈里的全部元素。就像是把数组的长度设置为0同样。spa

  六、获取栈里的元素个数。就像是数组的length属性同样。

  那么,我相信我你们已经对栈有了一个基本的了解,那么咱们接下来就看看如何经过构造函数来实现一个本身的js栈。

function Stack () {
    var items = [];

    //首先,咱们来实现一个入栈的方法,这个方法负责往栈里加入元素,要注意的是,该方法只能添加元素到栈顶,也就是栈的尾部。
    this.push = function (ele) {
        items.push(ele)
    }

}

var stack = new Stack();

  咱们声明一个构造函数,而且在构造函数中生命一个私有变量items,做为咱们Stack类储存栈元素的基本支持。而后,加入一个push方法,经过this来使其指向调用该方法的实例。下面咱们还会经过这样的方式依次添加其余的方法。

function Stack () {
    var items = [];

    //首先,咱们来实现一个入栈的方法,这个方法负责往栈里加入元素,要注意的是,该方法只能添加元素到栈顶,也就是栈的尾部。
    this.push = function (ele) {
        items.push(ele);
    }

    //而后咱们再添加一个出栈的方法,一样的,咱们只能移除栈顶的元素。
    this.pop = function (ele) {
        return items.pop();
    }

    //查看栈顶,也就是栈的尾部元素是什么
    this.peek = function () {
        return items[items.length - 1];
    }

    //检查栈是否为空
    this.isEmpty = function () {
        return items.length == 0;
    }

    //检查栈的长度
    this.size = function () {
        return items.length;
    }

    //清空栈
    this.clear = function () {
        items = [];
    }

    //打印栈内元素
    this.print = function () {
        console.log(items.toString())
    }
}

  这样咱们就经过构造函数完整的建立了一个栈。咱们能够经过new命令实例化一个Stack对象来测试一下咱们的栈好很差用。

var stack = new Stack();
console.log(stack.isEmpty());//true
stack.push(1);
stack.print();
stack.push(3);
stack.print();
console.log(stack.isEmpty());//false
console.log(stack.size());//2
stack.push(10);
stack.print();
stack.pop();
stack.print();
stack.clear();
console.log(stack.isEmpty());//true

  咱们发现咱们的Stack类执行的还算不错。那么还有没有其余的方式能够实现Stack类呢?在ES6以前我可能会遗憾懵懂的对你Say No。可是如今咱们能够一块儿来看看ES6带咱们的一些新鲜玩意。

   在开始改造咱们的Stack类以前,须要先说一下ES6的几个概念。Class语法Symbol基本类型WeakMap。简单解释一下,以对后面的改造不会一脸懵逼,而你们想要更深刻的了解ES6新增的各类语法,能够去自行查阅。

  Class语法简单来讲就是一个语法糖,它的功能ES5也是彻底能够实现的,只是这样看写起来更加清晰可读,更像是面向对象的语法。

  Symbol是ES6新增的一个基本类型,前面几篇文章说过,ES5只有6中数据类型,可是在ES6中又新增一种数据类型Symbol,它表示独一无二的值。

  WeakMap,简单来讲就是用于生成键值对的集合,就像是对象({})同样,WeakMap的一个重要用处就是部署私有属性。

  固然,上面的简单介绍可不只仅是这样的,真正的内容要比这些多得多。

  那么在你们知道了它们的一些基本意义。我们开始改造一下Stack类

class Stack {
  constructor() {
    this.items = [];
  }

  push(element) {
    this.items.push(element);
  }

  pop() {
    return this.items.pop();
  }

  peek() {
    return this.items[this.items.length - 1];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }

  clear() {
    this.items = [];
  }

  toString() {
    return this.items.toString();
  }

  print() {
      console.log(this.items.toString())
  }

}

  这是用class来实现的Stack类,其实咱们能够看一下,除了使用了constructor构造方法之外,其实并无什么本质上的区别。

  那么咱们还可使用Symbol数据类型来实现,简单改造一下:

const _items = Symbol('stackItems');

class Stack {
  constructor() {
    this[_items] = [];
  }

  push(element) {
    this[_items].push(element);
  }

  pop() {
    return this[_items].pop();
  }

  peek() {
    return this[_items][this[_items].length - 1];
  }

  isEmpty() {
    return this[_items].length === 0;
  }

  size() {
    return this[_items].length;
  }

  clear() {
    this[_items] = [];
  }

  print() {
    console.log(this.toString());
  }

  toString() {
    return this[_items].toString();
  }
}

  使用Symbol也没有大的变化,只是声明了一个独一无二的_items来代替构造方法中的数组。

  可是这样的实现方式有一个弊端,那就是ES6新增的Object.getOwnPropertySymbols方法能够读取到类里面声明的全部Symbols属性。

const stack = new Stack();
const objectSymbols = Object.getOwnPropertySymbols(stack);
stack.push(1);
stack.push(3);
console.log(objectSymbols.length); // 1
console.log(objectSymbols); // [Symbol()]
console.log(objectSymbols[0]); // Symbol()
stack[objectSymbols[0]].push(1);
stack.print(); // 1, 3, 1

  不知道你们注意没有,咱们定义的Symbol是在构造函数以外的,所以谁均可以改动它。因此这样的方式还不是很完善的。那么咱们还可使用ES6的WeakMap,而后用闭包实现私有属性。

//经过闭包把声明的变量变成私有属性
let Stack = (function () {
//声明栈的基本依赖
const _items = new WeakMap();
//声明计数器
const _count = new WeakMap();

class Stack {
  constructor() {
//初始化stack和计数器的值,这里的set是WeakMap的自身方法,经过set和get来设置值和取值,这里用this做为设置值的键名,那this又指向啥呢?自行console!
    _count.set(this, 0);
    _items.set(this, {});
  }

  push(element) {
//在入栈以前先获取长度和栈自己
    const items = _items.get(this);
    const count = _count.get(this);
//这里要注意_count但是从0开始的噢
    items[count] = element;
    _count.set(this, count + 1);
  }

  pop() {
//若是为空,那么则没法出栈
    if (this.isEmpty()) {
      return undefined;
    }
//获取items和count,使长度减小1
    const items = _items.get(this);
    let count = _count.get(this);
    count--;
//从新为_count赋值
    _count.set(this, count);
//删除出栈的元素,并返回该元素
    const result = items[count];
    delete items[count];
    return result;
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    const items = _items.get(this);
    const count = _count.get(this);
//返回栈顶元素
    return items[count - 1];
  }

  isEmpty() {
    return _count.get(this) === 0;
  }

  size() {
    return _count.get(this);
  }

  clear() {
    /* while (!this.isEmpty()) {
        this.pop();
      } */
    _count.set(this, 0);
    _items.set(this, {});
  }

  toString() {
    if (this.isEmpty()) {
      return '';
    }
    const items = _items.get(this);
    const count = _count.get(this);
    let objString = `${items[0]}`;
    for (let i = 1; i < count; i++) {
      objString = `${objString},${items[i]}`;
    }
    return objString;
  }

  print() {
      console.log(this.toString());
  }
}

return Stack;
})()

const stack = new Stack();
stack.push(1);
stack.push(3);
stack.print(); // 1, 3, 1

  这是最终比较完善的版本了。那么不知道你们注没注意到一个小细节,前面咱们只是声明一个变量,先无论他是否是私有的,就是数组,整个Stack构造函数都是基于items数组来进行各类方法的。

可是这里经过WeakMap做为基本,咱们却多用了一个_count,前面说了_count是计数器,那么为啥要用计数器?由于WeakMap是键值对的“对象类型”,自己是没有像数组这样的长度之说的,因此须要一个计数器来代替数组的下标,以实现基于Stack的各类方法。

   到这里基本上就完成了咱们的栈,下一篇文章会看看如何用咱们写好的栈去作一些有趣事情。

 

  最后,因为本人水平有限,能力与大神仍相差甚远,如有错误或不明之处,还望你们不吝赐教指正。很是感谢!

相关文章
相关标签/搜索