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

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

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

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

  对于数组来说,可以添加元素,删除元素,获取数组的长度以及返回对应下标得到值,那么在开始构造一个栈之前,我们需要了解一下栈都有哪些基本操作。

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

  2、出栈,移除栈顶的元素。就像是数组中的pop一样。

  3、获取栈顶的元素,不对栈做任何其他操作。就像是在数组中通过下标获取对应的值一样。

  4、判断栈是否为空。就像是判断数组的长度是否为0一样。

  5、清空栈,也就是移除栈里的所有元素。就像是把数组的长度设置为0一样。

  6、获取栈里的元素个数。就像是数组的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());//
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); //
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的各种方法。

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

  最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!

  

用js来实现那些数据结构(栈01)的更多相关文章

  1. js 实现数据结构 -- 栈

    原文: 在 Javascript 中学习数据结构与算法. 概念: 栈是一种遵从先进后出 (LIFO) 原则的有序集合:新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端为栈底.在栈里,新元素都靠 ...

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

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

  3. (js描述的)数据结构[栈结构](2)

    (js描述的)数据结构[栈结构](2) 一.什么是栈结构 1.一种受限制的线性结构,这种结构可以基于数组来实现. 2.可以抽象成一个容器,上面的是栈顶,底下的是栈底.所以仅允许对栈顶进行操作, 二.栈 ...

  4. 用js来实现那些数据结构—目录

    首先,有一点要声明,下面所有文章的所有内容的代码,都不是我一个人独立完成的,它们来自于一本叫做<学习JavaScript数据结构和算法>(第二版),人民邮电出版社出版的这本书.github ...

  5. 用js来实现那些数据结构及算法—目录

    首先,有一点要声明,下面所有文章的所有内容的代码,都不是我一个人独立完成的,它们来自于一本叫做<学习JavaScript数据结构和算法>(第二版),人民邮电出版社出版的这本书.github ...

  6. .NET的堆和栈01,基本概念、值类型内存分配

    当我们对.NET Framework的一些基本面了解之后,实际上,还是很有必要了解一些更底层的知识.比如.NET Framework是如何进行内存管理的,是如何垃圾回收的......这样,我们才能写出 ...

  7. js中非死循环引起的栈调用溢出问题

    一般情况下,仅从代码上看只要不出现死循环,是不会出现堆栈调用溢出的.但是某些情况下列外,比如下面这段代码: var a = 99; function b (){ a --; if (a > 0) ...

  8. 数据结构——栈(C语言实现)

    #include <stdio.h> #include <stdlib.h> #include<string.h> #include<malloc.h> ...

  9. C++ 泛型 编写的 数据结构 栈

    平时编程里经常需要用到数据结构,比如  栈和队列 等,  为了避免每次用到都需要重新编写的麻烦现将  C++ 编写的 数据结构   栈   记录下来,以备后用. 将 数据结构  栈   用头文件的形式 ...

随机推荐

  1. 基于Ubuntu的LNMP环境搭建

    装备的工具 Ubuntu16.04 , Xshell 使用Xshell链接到Ubuntu 使用xshell链接Ubuntu不是必须的,只是为了操作的方便,同时默认是你的Ubuntu已经安装好了 在Ub ...

  2. Sping Boot入门到实战之实战篇(一):实现自定义Spring Boot Starter——阿里云消息队列服务Starter

    在 Sping Boot入门到实战之入门篇(四):Spring Boot自动化配置 这篇中,我们知道Spring Boot自动化配置的实现,主要由如下几部分完成: @EnableAutoConfigu ...

  3. 小白学PYTHON时最容易犯的6个错误,看看你遇到过几个

    最近又在跟之前的同学一起学习python,一起进步,发现很多测试同学在初学python的时候很容易犯一些错误,特意总结了一下.其实这些错误不仅是在学python时会碰到,在学习其他语言的时候也同样会碰 ...

  4. [UWP]创建一个ProgressControl

    1. 前言 博客园终于新增了UWP的分类,我来为这个分类贡献第一篇博客吧. UWP有很多问题,先不说生态的事情,表单.验证.输入.设计等等一堆基本问题缠身.但我觉得最应该首先解决的绝对是Blend,那 ...

  5. XMAN-level4

    [XMAN] level4 首先checksec,信息如下 [*] '/root/Desktop/bin/pwn/xman-level4/level4' Arch: i386-32-little RE ...

  6. SpringMVC学习笔记三 整合jdbc和事务

    spring整合JDBC spring提供了很多模板整合Dao技术,用于简化编程. 引入相关jar包 spring中提供了一个可以操作数据库的对象,JDBCTemplate(JDBC模板对象).对象封 ...

  7. 利用1.1.1.1进行DNS网络加速,仅需2分钟让网络更快

    NEWS 近日,Cloudflare 和 APNIC联合推出了1.1.1.1DNS网络加速. Cloudflare 运行全球规模最大.速度最快的网络之一.APNIC 是一个非营利组织,管理着亚太和大洋 ...

  8. 2018上C语言程序设计(高级)作业- 第0次作业

    准备工作(10分) 1.在博客园申请个人博客. 2.加入班级博客(2班班级博客链接地址)(1班班级博客链接地址) 3.关注邹欣老师博客.关注任课老师博客. 4.加入讨论小组,学习过程中遇到问题不要随意 ...

  9. 解决python中flask_sqlalchemy包安装失败的问题

    在进行flask_sqlalchemy包的下载安装时出现以下问题: 由图片可看出是编码转换出了问题,找到pip\compat_init_.py文件,打开它并查看第73行,将代码做如下更改并保存: 问题 ...

  10. Python IDE Spyder的简单介绍

    最近深度学习发展非常迅猛,大有一统江湖的趋势.经过一段时间学习,发现自己对这种神奇的玄学非常感兴趣,希望能够进一步的研究.而这种研究性学科单纯地看论文比较难以明白,所以希望能够跟进大牛们写的代码深入学 ...