文章目录

  • 前言
  • 一、栈?
  • 二、构建两种栈的大致步骤
  • 三、创建基于数组的栈
    • 创建class Stack
    • 定义用于操作栈的方法
    • 使用栈
  • 四、创建基于对象的栈
    • 创建class Stack
    • 定义用于操作栈的方法
    • 使用栈
  • 五、保护数据结构内部元素
    • 演示:暴露的栈模型
    • 基于ES6 Symbol实现的半私有类
    • 基于ES6 weakMap实现的私有类
  • 六、用栈解决问题
  • 总结

前言

食用方法:请着重看下创建数组栈的步骤, 由于创建方便, 下半篇的例子大都基于数组栈来说明.
三四章看不懂请回来看第二章
请至少在看完前三章后再去看第五章…

上一篇:第三章 14000字笔记


一、栈?

栈,英文Stack(这个洋名很重要),下文会多次提及;

是一种遵从先进后出原则(LIFO)的有序结合,新添加或者待删除的元素都保存在栈的同一端即栈顶,那另一端自然就是栈底。
栈也可以用于在编译器和内存中保存变量和方法,另外,浏览器的历史记录和路由也和栈有关。


二、构建两种栈的大致步骤

书上讲述了两种栈的创建方式,两者之间存在大量的相似之处。

  1. 都要先声明一个Stack类:
class Stack {constructor() {//constructor方法内有不同}
}
  1. 都要定义一系列方法来预备对栈进行的操作(如存入和取出),但针对数组和对象构建的栈, 定义的方法自然不能相同;

  2. 在使用之前都要初始化Stack类:

const 自定义名 = new Stack();

三、创建基于数组的栈

创建一个Stack类最简单的方式是使用数组.

创建class Stack

class Stack {constructor() {this.items = [];//需要一种数据结构来存储栈中的元素, 这里选择了数组items.}
}

定义用于操作栈的方法

既然是基于数组的栈, 那我们就使用与数组方法同名的自定义方法吧…
考虑到函数同名的问题,个人不推荐把这些自定义方法写在class Stack外面;
举例:写了push之后JS会马上判定为数组的push方法并且报错你没加分号而不是判定它为一个自定义方法:

但是写在类里面你就不用担心这种奇怪的问题, 那我就直接展示在类里面的写法:

        class Stack {constructor() {this.items = [];}push(element) {  //向栈顶添加一个元素this.items.push(element);}pop() {  //从栈顶移除一个元素return this.items.pop();}peek() {  //查看栈顶元素,由于使用数组存储,最新加入的处于栈顶的元素可使用length-1取到;return this.items[this.items.length - 1];}isEmpty() {//检测当前栈是否为空;return this.items.length === 0;}size() {//返回栈的长度;return this.items.length;}clear() {//清空栈, 也可以多次调用pop来解决;this.items = [];}}

使用栈

第二节说了我们需要先初始化Stack类才能使用它, 一个不错的习惯是先初始化然后再验证一下其是否为空:

const myStack = new Stack();  //初始化;
console.log(myStack.isEmpty());  //true;

我这里使用的myStack是基于上面的Stack类存在的, 这部是在初始化上面声明的Stack类, 来去掉Stack类看看:

Uncaught ReferenceError: Stack is not defined

嗯, 然后这个栈就建完了, 来试一下吧:

let arr = [3, 5, 6];
myStack.push(...arr);
myStack.push("一个字符串");
console.log(myStack);

相当于myStack.push(3, 5, 6), 但是定义push方法时定义了只能传一个参;
故只有第一个参数3会被传入class Stack数组items里的第0位

另外在测试pop方法的时候发现栈似乎有些特性…
先push两个元素进去, 输出得到结果A.
然后pop移除栈顶的一个元素,刷新页面再次输出,两个结果都会是删除栈顶元素后的栈
即便第一次输出的代码排在pop之前:

console.log("初始栈是否为空:" + myStack.isEmpty());
let arr = [3, 5];
myStack.push(...arr);
myStack.push("一个字符串");
console.log(myStack);   //67行;
myStack.pop();
console.log(myStack); //70行;


四、创建基于对象的栈

与创建数组型栈的步骤很相似, 不做细说了.
即便十分方便, 用数组来创建栈依然存在诸多缺点, 若用n代表数组的长度,大部分方法访问数组时的时间复杂度是O(n), 这个公式的意思是我们需要迭代数组直到找到目标元素,甚至需要迭代整个数组。
此外,数组有序化的存储方式会占用更多的内存空间。

创建class Stack

        class Stack {constructor() {this.count = 0;this.items = {}//需要一种数据结构来存储栈中的元素, 这里选择了数组items.}

定义用于操作栈的方法

        class Stack {constructor() {this.count = 0;this.items = {}}push(element) {this.items[this.count] = element;this.count++;}pop(element) {if (this.isEmpty()) {return undefined;}this.count--;const result = this.items[this.count];  //将被删除的元素赋值给result;delete this.items[this.count];//delete操作符用于删除对象中的某个属性;return result;  //返回被删除的元素;}peek(element) {if (this.isEmpty()) {return undefined;}return this.items[this.count - 1]; //length-1取到栈顶值;}isEmpty(element) {return this.count === 0;}size() {return this.count;}toString () {//对象型栈无法使用toString数组方法,需要自定义方法来对栈内容进行打印;if(this.isEmpty()) {return '';}let objString = `${this.items[0]}`;  //使用最底部字符串作为初始值for(let i = 1; i < this.count; i++) {//遍历整个栈内的键直到栈顶;objString = `${objString}, ${this.items[i]}`;}return objString;}//这样下来除了toString方法, 其他几个方法的时间复杂度均为O(1), 也就是说都不用进行遍历.}

使用栈

const mystack = new Stack();
console.log(peek);
stack.push(5);
stack.push(8);

五、保护数据结构内部元素

“在创建别的开发者也可以使用的数据结构或对象时, 我们希望保护内部的元素,只有我们暴露出的方法才能修改其内部结构. 要确保元素只会被添加到栈顶, 而不是栈底或者其他什么地方, 不幸的是上面所举的栈并没有得到保护.”

“本章使用ES2015语法创建了Stack类, ES2015的类是基于原型的, 尽管基于原型的类可以节省内存空间而且在扩展方面优于基于函数的类, 但这种方式不能声明私有属性或方法.”

简而言之, 我们不希望栈内部的东西能被外面看到或者改写, 只允许人们从栈顶用我们定义好的方法进行操作, 将这一摞书外面套一层水泥管.

演示:暴露的栈模型

就常规的栈而言, 使用Object.getOwnPropertyNames()方法可以get到指定对象的所有属性名组成的数组, keys()也可, 就算直接输出都可以:

class Stack {constructor() {this.count = 0,  //count不能是字符串;this.items = [];}
}const stack = new Stack();
console.log(Object.getOwnPropertyNames(stack));
console.log(Object.keys(stack));

基于ES6 Symbol实现的半私有类

书中先尝试使用ES6 Symbol类型实现类, 来达到保护栈内元素不受损伤的目的.
Symbol是ES6新增的一种数据类型, 它是不可变的, 可用作对象的属性.
但是ES6对应的也伴生了getOwnPropertySymbols()方法可以破解这一保护措施:
“该种方法创建了一个假的私有属性, 因为ES6新增的Object.getOwnPropertySymbols方法能访问目标类里面所有的Symbols属性”

不论如何, 先看下这个例子(构建栈的方法还是没有做出改变的):

const _items = Symbol('stackItems');
//创建class Stack;
class Stack {constructor() {this[_items] = [];}//构建自定义方法push;push(element) {this[_items].push(element);}}//初始化Stack类, 准备进行操作;
const myStack = new Stack();
myStack.push(5);  //操作:压入元素5;
myStack.push(8);  //操作:压入元素8;let objectSymbols = Object.getOwnPropertySymbols(myStack);//此处相当于是把myStack这个类砸开了一个叫objectSymbols的缺口
//经由objectSymbols这个缺口我们可以访问到myStack类中的各种属性;

对比未执行getOwnPropertySymbols()的结果输出一下:

console.log(myStack);  //直接输出myStack
console.log(objectSymbols);   //输出经过处理的myStack
console.log(myStack.length);  //直接输出长度
console.log(objectSymbols.length);  //输出经过处理后的长度


经过处理后的myStack返回的是symbol属性stackItems, 由于处理后只能返回symbol属性, 最终结果的详细程度反倒不如直接输出myStack高;

基于ES6 weakMap实现的私有类

weakMap类型可以确保属性是私有的, weakMap可以存储键值对, 其中键是对象, 值可以是任意数据类型.

const items = new WeakMap();  //weakMap型变量items;
//将一个变量转换为WeakMap类与将其转换为Symbol类的方法很像吧?
class Stack {constructor() {items.set(this, []);  //将代表栈的数组存入items;}//定义用于操作栈的方法push(element) {const s = items.get(this);s.push(element);}pop() {const s = items.get(this);const r = s.pop();return r;}
}//初始化Stack类, 准备使用栈
const myStack = new Stack();
myStack.push(3);  //操作:压入3;
myStack.push(6);  //操作:压入6;

这是受保护的栈了, 我们输出一下看看大概就可以证明:

console.log(myStack);
console.log(Object.getOwnPropertyNames(myStack));
console.log(Object.keys(myStack));


依靠这种简单的手段, 我们几乎不能获取到任何栈内部的数据和构造信息, 不能直接访问.


六、用栈解决问题

栈的实际应用十分广泛.
在有回溯需求的问题上, 它可以存储访问过的任务或路径、撤销的操作.
Java和C#用栈来存储变量和方法调用.

书上举的例子是一个十进制转二进制的进制转换问题.

//创建基于数组的栈
class Stack {constructor() {this.count = 0;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;}
}function decimalToBinary(decNumber) {const remStack = new Stack();//在哪里使用栈就在哪里初始化栈;let number = decNumber;let rem;let binaryString = '';while (number > 0) {rem = Math.floor(number % 2);  //除法结果不为0时就将取整后的余数入栈remStack.push(rem);number = Math.floor(number / 2);  //除完一次取整继续除}while (!remStack.isEmpty()) {//二进制字符binaryString += remStack.pop().toString();  //将可出栈的元素链接为字符串, 清栈;}return binaryString;  //return出二进制数值
}console.log(decimalToBinary(33333));

总结

下篇将是第四章《队列和双端队列》,可能会咕一段时间了,最近出了好多事情,有点累了。

《学习JavaScript数据结构与算法》 第四章笔记 栈相关推荐

  1. 学习JavaScript数据结构与算法(一):栈与队列

    本系列的第一篇文章: 学习JavaScript数据结构与算法(一),栈与队列 第二篇文章:学习JavaScript数据结构与算法(二):链表 第三篇文章:学习JavaScript数据结构与算法(三): ...

  2. 读《学习JavaScript数据结构与算法》 第二章

    第二章 ECMAScript和TypeScript概述 文章目录 第二章 ECMAScript和TypeScript概述 前言 一.let const 二.模板字面量 支持换行 拼接方式 三.函数的默 ...

  3. 《学习JAVASCRIPT数据结构与算法》 ES6 部分笔记

    <!DOCTYPE html> Document let const 模板字面量 箭头函数 函数默认值 apply__ 属性简写 面向对象 posted @ 2017-12-02 22:5 ...

  4. 《学习JavaScript数据结构与算法》第三章 数组

    文章目录 前言 一.创建 && 初始化数组 二.操作数组 push-添加元素于末尾 unshift-添加元素于开头 pop-从数组末尾开始删除元素 shift-从数组开头开始删除元素 ...

  5. 重读《学习JavaScript数据结构与算法-第三版》- 第6章 链表(一)

    定场诗 伤情最是晚凉天,憔悴厮人不堪言: 邀酒摧肠三杯醉.寻香惊梦五更寒. 钗头凤斜卿有泪,荼蘼花了我无缘: 小楼寂寞新雨月.也难如钩也难圆. 前言 本章为重读<学习JavaScript数据结构 ...

  6. 为什么我要放弃javaScript数据结构与算法(第二章)—— 数组

    第二章 数组 几乎所有的编程语言都原生支持数组类型,因为数组是最简单的内存数据结构.JavaScript里也有数组类型,虽然它的第一个版本并没有支持数组.本章将深入学习数组数据结构和它的能力. 为什么 ...

  7. 学习JavaScript 数据结构与算法

    学习链接:https://github.com/XPoet/js-data-structures-and-algorithms JavaScript 数据结构与算法 1.数据结构(data struc ...

  8. 四大金刚 数据结构_学习JavaScript数据结构与算法(三):集合

    集合(Set遇新是直朋能到) 说起集合,就想起刚进高中时,数学第一课讲的就是集合.因此在学习集合这种数据结构时,倍感亲切. 集合的基本性质有一条: 集合中元素是不重复的.因为这种性质,所以我们选用了对 ...

  9. 《学习javascript数据结构与算法》——第六章:集合

    集合无序,元素唯一,以[值,值]的形式存储元素 创建集合 function Set() {var items = {};/*判断值是否在集合中*/this.has = function(value) ...

最新文章

  1. Workflow 4.0 中三种方式实现workflow的触发调用
  2. 《超低延时直播白皮书》已发布,推动直播延时降低90%以上
  3. php如何配置apache服务器,Apache服务器如何配置PHP
  4. C++编写DLL的方法
  5. 解决“The executable was signed with invalid entitlements.”问题
  6. oracle 全局id,基于SnowFlake 全局ID 生成器 go-id-worker
  7. 诺基亚:丑小鸭的重生
  8. PDF转图片文字丢失问题解决
  9. Matplotlib 可视化必备神书,附pdf下载
  10. 【Apollo 6.0项目实战】Canbus模块
  11. linux同花顺乱码,打开同花顺软件全是问号
  12. linux nas共享存储6,NAS(网络附属存储)技术
  13. 关于国际象棋皇后的递归问题——经典为8皇后
  14. 互联网糖水营销策略-科学城篇
  15. IDEA隐藏不想看见的文件
  16. 基于车辆运动学模型的LQR横向控制算法
  17. openssl加密解密
  18. elasticjob已下线_elasticJob 源码解析之自诊断恢复
  19. 河北工程大学计算机考试题型,2019上半年河北工程大学计算机等级考试报名通知...
  20. 新疆计算机系统集成资质年审,新疆涉密信息系统集成资质

热门文章

  1. 过半中国人是工具类 App 活跃用户,为什么变现却这么难?
  2. 为什么说 C++ 太复杂?有必要这么复杂吗?| 原力计划
  3. 港科大郑光廷院士问诊未来,揭露 AI 最新应用与实践
  4. 【正在直播】:CSDN直播间专属福利!1399买Airpods Pro
  5. 微信小范围上线“分付”功能;罗永浩宣布独家签约抖音;Github pages 可能遭遇中间人攻击 | 极客头条...
  6. 隔离是否有效?北大面向新冠疫情的数据可视化分析与模拟预测
  7. 文末有福利 | 6大理由,告诉你为什么这个大会你不能错过!
  8. 彻底火了!这份Python学习贴,90%程序员用的上!
  9. 知名社交网络 Myspace 丢失 12 年用户数据,大型系统究竟如何做迁移?
  10. Python 分析 35 年的考研英语真题词汇,解读孤独的考研大军!