用 ES6 的 WeakMap 实 现 类
有 一 种 数 据 类 型 可 以 确 保 属 性 是 私 有 的, 这 就是 WeakMap。 我 们 会 在 后 面 深 入 探 讨 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 中, 以 this( Stack 类 自 己 的 引 用) 为 键, 把 代 表 栈 的
行{ 3}, 从 WeakMap 中 取 出 值, 即 以 this 为 键( 行{ 2} 设 置 的) 从 items 中 取 值。
现 在 我 们 知 道, items 在 Stack 类 里 是 真 正 的 私 有 属 性 了, 但 还 有 一 件 事 要 做。 items 现 在 仍 然 是 在 Stack 类 以 外 声 明 的, 因 此 谁 都 可 以 改 动 它。 我 们 要 用 一 个 闭 包( 外 层 函 数) 把 Stack 类 包 起 来, 这 样 就 只 能 在 这 个 函 数 里 访 问 WeakMap:
1 let Stack = (function () {
2 const items = new WeakMap();
3 class Stack {
4 constructor() {
5 items.set(this, []);
6 } // 其 他 方 法
7 }
8 return Stack; //{5}
9 })();
当 Stack 函 数 里 的 构 造 函 数 被 调 用 时, 会 返 回 Stack 类 的 一 个 实 例( 行{ 5})。
栈 的 实 际 应 用 非 常 广 泛。 在 回 溯 问 题 中, 它 可 以 存 储 访 问 过 的 任 务 或 路 径、 撤 销 的 操 作( 后 面 的 章 节 讨 论 图 和 回 溯 问 题 时, 我 们 会 学 习 如 何 应 用 这 个 例 子)。 Java 和 C# 用 栈 来 存 储 变 量 和 方 法 调 用, 特 别 是 处 理 递 归 算 法 时, 有 可 能 抛 出 一 个 栈 溢 出 异 常( 后 面 的 章 节 也 会 介 绍)。 既 然 我 们 已 经 了 解 了 Stack 类 的 用 法, 不 妨 用 它 来 解 决 一 些 计 算 机 科 学 问 题。 本 节, 我 们 将 学 习 使 用 栈 的 三 个 最 著 名 的 算 法 示 例。 首 先 是 十 进 制 转 二 进 制 问 题, 以 及 任 意 进 制 转 换 的 算 法; 然 后 是 平 衡 圆 括 号 问 题; 最 后, 我 们 会 学 习 如 何 用 栈 解 决 汉 诺 塔 问 题。
从 十 进 制 到 二 进 制
现 实 生 活 中, 我 们 主 要 使 用 十 进 制。 但 在 计 算 科 学 中, 二 进 制 非 常 重 要, 因 为 计 算 机 里 的 所 有 内 容 都 是 用 二 进 制 数 字 表 示 的( 0 和 1)。 没 有 十 进 制 和 二 进 制 相 互 转 化 的 能 力, 与 计 算 机 交 流 就 很 困 难。 要 把 十 进 制 转 化 成 二 进 制, 我 们 可 以 将 该 十 进 制 数 字 和 2 整 除( 二 进 制 是 满 二 进 一), 直 到 结 果 是 0 为 止。 举 个 例 子, 把 十 进 制 的 数 字 10 转 化 成 二 进 制 的 数 字, 过 程 大 概 是 这 样:
大 学 的 计 算 机 课 一 般 都 会 先 教 这 个 进 制 转 换。 下 面 是 对 应 的 算 法 描 述:
1 function divideBy2(decNumber) {
2 var remStack = new Stack(),
3 rem, binaryString = '';
4 while (decNumber > 0) { //{1}
5 rem = Math.floor(decNumber % 2); //{2}
6 remStack.push(rem); //{3}
7 decNumber = Math.floor(decNumber / 2); //{4}
8 }
9 while (!remStack.isEmpty()) { //{5}
10 binaryString += remStack.pop().toString();
11 }
12 return binaryString;
13 }
在 这 段 代 码 里, 当 结 果 满 足 和 2 做 整 除 的 条 件 时( 行{ 1}), 我 们 会 获 得 当 前 结 果 和 2 的 余 数, 放 到 栈 里( 行{ 2}、{ 3})。 然 后 让 结 果 和 2 做 整 除( 行{ 4})。 另 外 请 注 意: JavaScript 有 数 字 类 型, 但 是 它 不 会 区 分 究 竟 是 整 数 还 是 浮 点 数。 因 此, 要 使 用 Math.floor 函 数 让 除 法 的 操 作 仅 返 回 整 数 部 分。 最 后, 用 pop 方 法 把 栈 中 的 元 素 都 移 除, 把 出 栈 的 元 素 变 成 连 接 成 字 符 串( 行{ 5})。 用 刚 才 写 的 算 法 做 一 些 测 试, 使 用 以 下 代 码 把 结 果 输 出 到 控 制 台 里:
1 console.log(divideBy2(233));
2 console.log(divideBy2(10));
3 console.log(divideBy2(1000));
进 制 转 换 算 法
我 们 很 容 易 修 改 之 前 的 算 法, 使 之 能 把 十 进 制 转 换 成 任 何 进 制。 除 了 让 十 进 制 数 字 和 2 整 除 转 成 二 进 制 数, 还 可 以 传 入 其 他 任 意 进 制 的 基 数 为 参 数, 就 像 下 面 算 法 这 样:
1 function baseConverter(decNumber, base) {
2 var remStack = new Stack(),
3 rem, baseString = '',
4 digits = '0123456789ABCDEF'; //{6}
5 while (decNumber > 0) {
6 rem = Math.floor(decNumber % base);
7 remStack.push(rem);
8 decNumber = Math.floor(decNumber / base);
9 }
10 while (!remStack.isEmpty()) {
11 baseString += digits[remStack.pop()]; //{7}
12 }
13 return baseString;
14 }
我 们 只 需 要 改 变 一 个 地 方。 在 将 十 进 制 转 成 二 进 制 时, 余 数 是 0 或 1; 在 将 十 进 制 转 成 八 进 制 时, 余 数 是 0 到 7 之 间 的 数; 但 是 将 十 进 制 转 成 16 进 制 时, 余 数 是 0 到 9 之 间 的 数 字 加 上 A、 B、 C、 D、 E 和 F( 对 应 10、 11、 12、 13、 14 和 15)。 因 此, 我 们 需 要 对 栈 中 的 数 字 做 个 转 化 才 可 以( 行{ 6} 和 行{ 7})。
可 以 使 用 之 前 的 算 法, 输 出 结 果 如 下:
1 console.log(baseConverter(100345, 2));
2 console.log(baseConverter(100345, 8));
3 console.log(baseConverter(100345, 16));
回文判断
1 //回文判断 2 3 function isPalindrome ( word ) { 4 var s = new Stack(); 5 for( var i = 0 ; i < word.length ; i ++ ){ 6 s.push( word[i] ); 7 } 8 var rword = ''; 9 while( s.length() > 0 ){ 10 rword += s.pop(); 11 } 12 13 if( word == rword ){ 14 return true; 15 }else{ 16 return false; 17 } 18 } 19 20 console.log( isPalindrome('level') ) // true 21 console.log( isPalindrome('1001') ) // true 22 console.log( isPalindrome('word') ) // false