Lodash源码阅读-Stack

Lodash 源码阅读-Stack

概述

Stack 是 Lodash 内部使用的栈结构实现,主要用于缓存键值对数据。它有一个特殊的性能优化策略:当数据量较小时使用简单的数组存储,当数据量增大时自动切换到 Map 结构存储,以提高大数据量下的性能。

前置学习

  • ListCache:用于小数据量存储的简单键值对集合,基于数组实现
  • MapCache:用于大数据量存储的键值对集合,基于 Map 实现
  • getNative:用于获取原生 JavaScript 对象(如 Map)

技术知识:

  • JavaScript 的数据结构:数组和 Map
  • JavaScript 中的原型继承
  • 性能优化策略

源码实现

javascript 复制代码
/**
 * Creates a stack cache object to store key-value pairs.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
function Stack(entries) {
  var data = (this.__data__ = new ListCache(entries));
  this.size = data.size;
}

/**
 * Removes all key-value entries from the stack.
 *
 * @private
 * @name clear
 * @memberOf Stack
 */
function stackClear() {
  this.__data__ = new ListCache();
  this.size = 0;
}

/**
 * Removes `key` and its value from the stack.
 *
 * @private
 * @name delete
 * @memberOf Stack
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
function stackDelete(key) {
  var data = this.__data__,
    result = data["delete"](key);

  this.size = data.size;
  return result;
}

/**
 * Gets the stack value for `key`.
 *
 * @private
 * @name get
 * @memberOf Stack
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
function stackGet(key) {
  return this.__data__.get(key);
}

/**
 * Checks if a stack value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf Stack
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
function stackHas(key) {
  return this.__data__.has(key);
}

/**
 * Sets the stack `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf Stack
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the stack cache instance.
 */
function stackSet(key, value) {
  var data = this.__data__;
  if (data instanceof ListCache) {
    var pairs = data.__data__;
    if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
      pairs.push([key, value]);
      this.size = ++data.size;
      return this;
    }
    data = this.__data__ = new MapCache(pairs);
  }
  data.set(key, value);
  this.size = data.size;
  return this;
}

// Add methods to `Stack`.
Stack.prototype.clear = stackClear;
Stack.prototype["delete"] = stackDelete;
Stack.prototype.get = stackGet;
Stack.prototype.has = stackHas;
Stack.prototype.set = stackSet;

实现思路

Stack 数据结构的核心思想是通过自动切换底层实现来保持高效性能。它使用两种不同的缓存机制:

  1. 对于少量数据(数组长度 < 199),使用基于数组的 ListCache
  2. 当数据量增大时(数组长度 >= 199),自动切换到基于 Map 的 MapCache

这种策略结合了数组在小数据量时的简单高效和 Map 在大数据量时的查找性能优势。Stack 类提供了标准的增删改查接口(clear、delete、get、has、set),通过原型方法实现。

源码解析

构造函数

javascript 复制代码
function Stack(entries) {
  var data = (this.__data__ = new ListCache(entries));
  this.size = data.size;
}

构造函数接收一个可选的 entries 参数,这是一个键值对数组。它创建了一个 ListCache 实例用于初始数据存储,然后将其赋值给 __data__ 属性,并同步 size 属性。

例如:

javascript 复制代码
const stack = new Stack([
  ["key1", "value1"],
  ["key2", "value2"],
]);
// 内部结构:
// stack.__data__.__data__ = [['key1', 'value1'], ['key2', 'value2']]
// stack.size = 2

clear 方法

javascript 复制代码
function stackClear() {
  this.__data__ = new ListCache();
  this.size = 0;
}

清空栈的所有数据,重置为一个新的 ListCache 实例,并将 size 设为 0。

例如:

javascript 复制代码
stack.clear();
// 执行后:
// stack.__data__.__data__ = []
// stack.size = 0

delete 方法

javascript 复制代码
function stackDelete(key) {
  var data = this.__data__,
    result = data["delete"](key);

  this.size = data.size;
  return result;
}

删除指定键的数据,并返回删除操作是否成功。内部委托给 __data__ 的 delete 方法,然后同步 size 属性。

例如:

javascript 复制代码
const result = stack.delete("key1");
// 如果key1存在,则result为true,并且size减1
// 如果key1不存在,则result为false,size不变

get 方法

javascript 复制代码
function stackGet(key) {
  return this.__data__.get(key);
}

获取指定键的值,内部直接委托给 __data__ 的 get 方法。

例如:

javascript 复制代码
const value = stack.get("key2");
// 如果key2存在,则返回对应的值
// 如果key2不存在,则返回undefined

has 方法

javascript 复制代码
function stackHas(key) {
  return this.__data__.has(key);
}

检查是否存在指定键,内部直接委托给 __data__ 的 has 方法。

例如:

javascript 复制代码
const exists = stack.has("key2");
// 如果key2存在,则返回true
// 如果key2不存在,则返回false

set 方法

javascript 复制代码
function stackSet(key, value) {
  var data = this.__data__;
  if (data instanceof ListCache) {
    var pairs = data.__data__;
    if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
      pairs.push([key, value]);
      this.size = ++data.size;
      return this;
    }
    data = this.__data__ = new MapCache(pairs);
  }
  data.set(key, value);
  this.size = data.size;
  return this;
}

这是 Stack 类中最复杂的方法,也是性能优化的核心。流程如下:

  1. 检查当前存储结构是否为 ListCache
  2. 如果是 ListCache,则检查:
    • Map 构造函数是否不存在,或者
    • 当前数据数量是否小于 LARGE_ARRAY_SIZE - 1 (199)
  3. 如果条件满足,则将新键值对直接添加到数组中
  4. 如果条件不满足(数据量达到阈值),则创建一个新的 MapCache,并将数据迁移过去
  5. 无论当前存储结构是什么,最后都调用其 set 方法设置键值对
  6. 同步 size 属性并返回实例本身,支持链式调用

例如:

javascript 复制代码
// 当数据量小时
stack.set("key3", "value3");
// 直接添加到数组:stack.__data__.__data__ = [...原数据, ['key3', 'value3']]

// 当数据量接近200时
stack.set("key200", "value200");
// 可能触发从ListCache到MapCache的转换
// 转换后:stack.__data__ 变成 MapCache 实例

原型方法绑定

javascript 复制代码
Stack.prototype.clear = stackClear;
Stack.prototype["delete"] = stackDelete;
Stack.prototype.get = stackGet;
Stack.prototype.has = stackHas;
Stack.prototype.set = stackSet;

这部分将各个操作函数绑定到 Stack 的原型上。注意 delete 方法使用方括号语法 ['delete'] 而不是点语法,这是因为 delete 是 JavaScript 的保留字。

应用场景

Stack 在 Lodash 内部主要用于:

  1. 深拷贝和克隆操作:在处理循环引用时维护已访问对象
  2. 相等性比较:在比较对象时缓存已比较过的对象对
  3. 去重操作:在 uniq、union 等函数中缓存已见过的值

总结

Stack 是 Lodash 内部使用的高效缓存结构,其核心设计思想是根据数据量大小自动切换底层实现。这种设计体现了以下软件设计原则:

  1. 适应性策略模式:根据数据规模动态选择最佳的数据结构实现
  2. 封装变化:对外提供统一接口,内部实现可以灵活变化
  3. 性能优化:小数据量用数组保持简单高效,大数据量用 Map 保持查找性能

这种实现方式对我们有以下启示:

  1. 在构建数据结构时,可以综合考虑不同数据规模下的性能特点
  2. 通过对内部实现的抽象和封装,可以在不影响外部接口的情况下优化性能
  3. 充分利用 JavaScript 原生对象(如 Map)的特性来提升性能
相关推荐
江城开朗的豌豆22 分钟前
JavaScript篇:构造函数 vs Class:谁才是对象创建的王者?
前端·javascript·面试
江城开朗的豌豆25 分钟前
JavaScript篇:数组找不同:如何快速找出两个数组间的'单身狗'元素?
前端·javascript·面试
几道之旅26 分钟前
python-pptx去除形状默认的阴影
开发语言·javascript·python
不吃鱼的羊1 小时前
ISOLAR软件生成报错处理(七)
java·前端·javascript
TE-茶叶蛋1 小时前
React-props
前端·javascript·react.js
安分小尧1 小时前
[特殊字符] 超强 Web React版 PDF 阅读器!支持分页、缩放、旋转、全屏、懒加载、缩略图!
前端·javascript·react.js
EndingCoder1 小时前
React从基础入门到高级实战:React 高级主题 - React Concurrent 特性:深入探索与实践指南
前端·javascript·react.js·前端框架
EndingCoder1 小时前
React从基础入门到高级实战:React 生态与工具 - React Query:异步状态管理
前端·javascript·react.js·前端框架
TE-茶叶蛋1 小时前
ReactJS 中的 JSX工作原理
前端·react.js·前端框架
水煮白菜王1 小时前
React 编译器
前端·react.js·前端框架