Lodash 源码解读与原理分析 - Lodash 原型链的完整结构

Lodash 的原型链体系以多构造函数分层设计为核心,通过原型继承串联起不同功能的包装器,形成清晰的层级依赖关系。这一设计既保证了方法的复用性,又实现了不同包装器的功能差异化,是链式调用和惰性求值的基础。

核心构造函数与原型对象的关系

Lodash 围绕"包装器"核心设计了多个构造函数,每个构造函数对应专属原型对象,通过原型继承实现方法复用与功能扩展。各构造函数与原型对象的关联的关系、职责划分如下表所示,涵盖了从基础包装到惰性求值、模板处理的全场景需求。

构造函数 原型对象 继承自 主要职责 原型上的核心方法示例 设计初衷
baseLodash baseLodash.prototype Object.prototype 基础构造函数,所有包装器的原型链起点 value()、chain()、toString() 抽离通用方法,实现复用
lodash baseLodash.prototype Object.prototype 主函数(即 _ 函数),实现链式调用的入口 无(复用 baseLodash.prototype 方法) 统一入口,简化调用
LodashWrapper LodashWrapper.prototype baseLodash.prototype 具体的包装器实现,处理普通链式调用 map ()、filter ()、take ()、value ()(重写) 封装非惰性操作队列
LazyWrapper LazyWrapper.prototype baseLodash.prototype 惰性求值包装器,优化大数据集操作性能 map ()、filter ()、take ()、value ()(重写) 延迟执行,减少遍历次数
Template Template.prototype Function.prototype 模板函数构造器,处理模板字符串编译 render()、source() 封装模板解析逻辑

详细的原型链结构 :不同包装器实例的原型链遵循"子类实例→子类原型→父类原型→Object.prototype→null"的规则,确保方法查找和属性继承的正确性。以下是三种核心实例的完整原型链拆解:

js 复制代码
// 普通 LodashWrapper 实例的原型链
LodashWrapper 实例
  ↑ __proto__
LodashWrapper.prototype
  ↑ __proto__
baseLodash.prototype
  ↑ __proto__
Object.prototype
  ↑ __proto__
null

// LazyWrapper 实例的原型链
LazyWrapper 实例
  ↑ __proto__
LazyWrapper.prototype
  ↑ __proto__
baseLodash.prototype
  ↑ __proto__
Object.prototype
  ↑ __proto__
null

// Template 实例的原型链
Template 实例 (函数)
  ↑ __proto__
Template.prototype
  ↑ __proto__
Function.prototype
  ↑ __proto__
Object.prototype
  ↑ __proto__
null

关键实现细节 :Lodash 采用 baseCreate 函数实现原型继承(兼容 ES5 之前环境,等效于 Object.create),确保原型链的正确挂载,同时重置构造函数指向,避免原型继承导致的构造函数混乱。核心源码如下:

关键实现细节(补充逐行注释 + 执行示例)

原文代码保留,补充逐行注释属性说明,让新手也能理解每一行的作用:

js 复制代码
// 1. 实现 baseCreate 函数,用于基于原型创建新对象(兼容 IE8 及以下低版本浏览器(这类环境不支持 `Object.create`),同时通过空构造函数 `Ctor` 避免原型对象上的构造逻辑被意外执行,提升继承安全性。)
// 核心作用:替代原生 Object.create(),实现"原型继承"的底层能力
var baseCreate = (function() {
  // 定义空构造函数,用于临时挂载原型
  function object() {}
  return function(proto) {
    // 边界处理:如果传入的原型不是对象,返回空对象
    if (!isObject(proto)) {
      return {};
    }
    // 优先使用 ES5 原生 Object.create()(性能更好)
    if (objectCreate) {
      return objectCreate(proto);
    }
    // 兼容低版本环境:通过构造函数模拟 Object.create()
    object.prototype = proto; // 将空构造函数的原型指向目标原型
    var result = new object;  // 创建实例,实例的 __proto__ 指向 proto
    object.prototype = undefined; // 重置原型,避免污染
    return result;
  };
}());

// 2. 创建 baseLodash 构造函数和原型(所有包装器的根原型)
function baseLodash() {
  // No operation performed. 空函数:仅作为原型挂载的载体,无需执行逻辑
}

// 3. 确保 lodash 主函数(即 _ 函数)的原型指向 baseLodash.prototype
// 作用:让 _ 函数的实例能继承 baseLodash.prototype 上的通用方法(如 value())
lodash.prototype = baseLodash.prototype;
lodash.prototype.constructor = lodash; // 修正构造函数指向,避免原型链混乱

// 4. 创建 LodashWrapper.prototype,继承自 baseLodash.prototype
// 构造函数作用:封装普通链式调用的状态和操作队列
function LodashWrapper(value, chainAll) {
  this.__wrapped__ = value;    // 核心:存储被包装的原始值(如数组、对象)
  this.__actions__ = [];       // 存储待执行的操作队列(如 map、filter)
  this.__chain__ = !!chainAll; // 标记是否开启链式调用(true 则返回实例,false 则返回结果)
  this.__index__ = 0;          // 辅助:遍历操作队列时的索引
  this.__values__ = undefined; // 辅助:缓存操作执行后的结果
}
// 核心:让 LodashWrapper.prototype 继承 baseLodash.prototype
LodashWrapper.prototype = baseCreate(baseLodash.prototype);
LodashWrapper.prototype.constructor = LodashWrapper; // 修正构造函数指向

// 5. 创建 LazyWrapper.prototype,继承自 baseLodash.prototype
// 构造函数作用:封装惰性求值的状态和操作队列,优化大数据集性能
function LazyWrapper(value) {
  this.__wrapped__ = value;       // 存储被包装的原始值(通常是数组)
  this.__actions__ = [];          // 存储待执行的惰性操作队列
  this.__dir__ = 1;               // 遍历方向:1 正向(从0开始),-1 反向(从末尾开始)
  this.__filtered__ = false;      // 标记是否执行过 filter 操作,优化遍历逻辑
  this.__iteratees__ = [];        // 存储迭代器函数(如 map 的回调、filter 的断言)
  this.__takeCount__ = MAX_ARRAY_LENGTH; // take() 方法的限制数量,默认最大数组长度
  this.__views__ = [];            // 辅助:存储数组视图,避免重复创建
}
// 核心:让 LazyWrapper.prototype 继承 baseLodash.prototype
LazyWrapper.prototype = baseCreate(baseLodash.prototype);
LazyWrapper.prototype.constructor = LazyWrapper; // 修正构造函数指向

baseCreate 执行示例

js 复制代码
// 调用 baseCreate 创建继承自 baseLodash.prototype 的对象
var testProto = baseCreate(baseLodash.prototype);
console.log(Object.getPrototypeOf(testProto) === baseLodash.prototype); // true
console.log(testProto.constructor === baseLodash); // true(未修正前)

原型方法的挂载与继承

Lodash 采用"分层挂载、按需重写"的策略管理原型方法:基础方法挂载在顶层原型,子类原型按需重写或扩展方法,既保证代码复用,又实现功能差异化。这种设计让不同包装器既能共享通用逻辑,又能拥有专属核心能力。

原型方法的挂载流程

  • 基础方法挂载 :Lodash 初始化时,先在 baseLodash.prototype 上挂载最基础的方法(如 value()chain()toString()),这些方法是所有包装器的通用能力,无需重复实现。

  • 扩展方法挂载 :在 LodashWrapper.prototype 上挂载具体的操作方法(如 map()filter()take()),这些方法针对普通链式调用设计,执行时会将操作加入队列。

  • 重写方法 :在 LazyWrapper.prototype 上重写部分方法(如 map()filter()value()),实现惰性求值逻辑 ------ 不立即执行操作,仅存储操作队列,直到调用 value() 才批量执行。

  • 特殊方法挂载Template.prototype 单独挂载模板相关方法(如 render()),因继承自 Function.prototype,可直接作为函数执行,兼具模板渲染能力。

value() 示例: 方法的继承与重写value()是 Lodash 包装器的核心方法,用于触发操作执行并返回结果,其实现随原型链层级逐步重写,适配不同包装器的功能需求,是"多态设计"的典型体现。

js 复制代码
// baseLodash.prototype.value - 基础实现:仅返回包装的原始值
baseLodash.prototype.value = function() {
  return this.__wrapped__;
};

// LodashWrapper.prototype.value - 重写实现,执行操作队列
// 逻辑:遍历操作队列,依次执行每个操作,返回最终结果
LodashWrapper.prototype.value = function() {
  var value = this.__wrapped__;
  for (var i = 0, length = this.__actions__.length; i < length; i++) {
    var action = this.__actions__[i];
    // 执行操作:将原始值作为第一个参数,拼接 action.args 作为后续参数
    value = action.func.apply(action.thisArg, [value].concat(action.args));
  }
  return value;
};

// LazyWrapper.prototype.value - 再次重写,实现惰性求值
// 逻辑:单次遍历原始数组,批量执行所有操作,支持短路终止(take() 触发)
LazyWrapper.prototype.value = function() {
  var array = this.__wrapped__,
      length = array.length,
      actions = this.__actions__,
      iteratees = this.__iteratees__,
      dir = this.__dir__,
      index = dir > 0 ? -1 : length, // 根据遍历方向初始化索引
      result = [];

  // 按方向遍历数组(正向/反向)
  while ((dir > 0 ? ++index < length : --index >= 0)) {
    var value = array[index],
        // 核心:对当前元素执行所有操作(map/filter 等)
        computed = this.__compute(value, index, array, actions, iteratees);
    
    // filter 筛选通过(computed 不为 undefined)则加入结果
    if (computed !== undefined) {
      result.push(computed);
      // 短路优化:达到 takeCount 则终止遍历
      if (this.__takeCount__ != null && result.length >= this.__takeCount__) {
        break;
      }
    }
  }

  return result;
};

value () 方法执行示例

js 复制代码
// 1. baseLodash.prototype.value 执行示例
var baseInstance = new baseLodash();
baseInstance.__wrapped__ = [1,2,3];
console.log(baseInstance.value()); // 输出:[1,2,3]

// 2. LodashWrapper.prototype.value 执行示例
var normalWrapper = new LodashWrapper([1,2,3], true);
normalWrapper.__actions__.push({
  func: _.map,
  args: [n => n*2],
  thisArg: undefined
});
console.log(normalWrapper.value()); // 输出:[2,4,6]

// 3. LazyWrapper.prototype.value 执行示例
var lazyWrapper = new LazyWrapper([1,2,3,4]);
lazyWrapper.__actions__.push({ func: arrayMap, args: [n => n*2] });
lazyWrapper.__actions__.push({ func: arrayFilter, args: [n => n>4] });
lazyWrapper.__takeCount__ = 2;
console.log(lazyWrapper.value()); // 输出:[6,8]

方法查找与执行机制

当调用包装器实例的方法时(如 wrapper.map(iteratee)),JavaScript 会遵循"原型链查找规则"定位方法,找到后根据包装器类型执行对应逻辑。这一机制确保了不同包装器能复用同名方法,同时执行差异化逻辑。

wrapper.map(iteratee) 当调用 时的详细执行流程

第一步:方法查找(原型链向上遍历)

  • 首先在 wrapper 实例自身查找 map 方法(不存在)。

  • 然后沿着 __proto__ 链向上查找:

    • 如果 wrapperLazyWrapper 实例,在 LazyWrapper.prototype 中查找 map 方法。
    • 如果 LazyWrapper.prototype 中没有,继续在 baseLodash.prototype 中查找。
    • 如果 wrapperLodashWrapper 实例,在 LodashWrapper.prototype 中查找 map 方法。
    • 如果 LodashWrapper.prototype 中没有,继续在 baseLodash.prototype 中查找。
    • 如果 baseLodash.prototype 中没有,继续在 Object.prototype 中查找(通常不会到这一步)。

第二步:方法执行(根据包装器类型差异化执行)

找到 map 方法后,根据包装器类型执行对应逻辑,核心差异在于是否开启惰性求值:

执行惰性版本 map 方法,核心逻辑是"创建新包装器、添加操作到队列、返回新实例",不立即执行遍历操作。具体实现:

对于 LazyWrapper 实例

js 复制代码
LazyWrapper.prototype.map = function(iteratee) {
  // 创建新的 LazyWrapper 实例(immutable 设计,不修改原实例)
  var wrapper = new LazyWrapper(this.__wrapped__);
  // 复制原实例的所有属性,保证状态一致
  wrapper.__actions__ = this.__actions__.concat({
    'func': arrayMap,  // 底层执行函数(数组原生 map 优化版)
    'args': [iteratee], // 传入用户定义的迭代器
    'thisArg': undefined // 不绑定 this,使用默认上下文
  });
  // 复制其他惰性相关属性
  wrapper.__dir__ = this.__dir__;
  wrapper.__filtered__ = this.__filtered__;
  wrapper.__iteratees__ = this.__iteratees__;
  wrapper.__takeCount__ = this.__takeCount__;
  // 返回新实例,支持链式调用(惰性模式默认开启链式)
  return wrapper;
};

对于 LodashWrapper 实例

执行非惰性版本 map 方法,核心逻辑是"添加操作到队列、根据链式标记返回结果",非链式模式下立即执行操作。具体实现:

js 复制代码
LodashWrapper.prototype.map = function(iteratee) {
  // 创建操作对象,封装执行函数、参数和上下文
  var action = {
    'func': _.map,         // 复用静态方法 _.map 的核心逻辑
    'args': [iteratee],    // 迭代器参数
    'thisArg': undefined   // 上下文绑定
  };
  // 将操作加入队列,延迟执行
  this.__actions__.push(action);
  // 链式模式返回 this(继续链式调用),非链式模式立即执行并返回结果
  return this.__chain__ ? this : this.value();
};
相关推荐
小高0072 小时前
2026 年,只会写 div 和 css 的前端将彻底失业
前端·javascript·vue.js
梁森的掘金2 小时前
Frida Hook 流程
前端
www_stdio2 小时前
Git 提交AI神器:用大模型帮你写出规范的 Commit Message
前端·javascript·react.js
陈随易2 小时前
Bun v1.3.6发布,内置tar解压缩,各方面提速又提速
前端·后端
双向332 小时前
【AIGC爆款内容生成全攻略:如何用AI颠覆内容创作效率?】
前端
陈_杨2 小时前
前端成功转鸿蒙开发者真实案例,教大家如何开发鸿蒙APP-- 卡片编辑功能
前端·harmonyos
Swift社区2 小时前
Flutter 的异步问题,为什么和前端 Promise 问题高度相似?
前端·flutter
程序员Agions2 小时前
AI 编程的"效率幻觉":为什么用了 Cursor 之后,你反而更累了?
前端·ai编程
Android技术之家2 小时前
在手机上跑大模型?Google AI Edge Gallery 开源项目深度解析
前端·人工智能·edge·开源