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)的特性来提升性能
相关推荐
hikktn7 分钟前
【开源宝藏】30天学会CSS - DAY6 第六课 流光文字动画
前端·css·开源
trust Tomorrow9 分钟前
JavaScript案例0322
javascript
samroom25 分钟前
React-Router路由跳转、传参、抽象封装以及嵌套路由
前端·react.js·前端框架
magic 24538 分钟前
移动端WEB开发之响应式布局
前端·css·html·html5
胡桃夹夹子41 分钟前
uniapp自身bug | uniapp+vue3打包后 index.html无法直接运行
javascript·uni-app·bug
fridayCodeFly42 分钟前
:class=“{ ‘addCheckstyle‘: hasError }“这是什么意思
前端·javascript·vue.js
知识分享小能手44 分钟前
CSS3学习教程,从入门到精通,CSS3 布局语法知识点及案例代码(15)
前端·css·学习·html·css3·html5·java后端开发
木木黄木木1 小时前
使用CSS3实现炫酷的3D翻转卡片效果
前端·3d·css3
Sperains1 小时前
响应式数组操作在Vue3和React中的差异
前端
阿黄学技术1 小时前
Spring框架核心注解(Spring,SpringMVC,SpringBoot)
前端·spring boot·spring