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

52 阅读8分钟

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

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

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

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

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

// 普通 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),确保原型链的正确挂载,同时重置构造函数指向,避免原型继承导致的构造函数混乱。核心源码如下:

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

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

// 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 执行示例

// 调用 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 包装器的核心方法,用于触发操作执行并返回结果,其实现随原型链层级逐步重写,适配不同包装器的功能需求,是“多态设计”的典型体现。

// 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 () 方法执行示例

// 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 实例

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 方法,核心逻辑是“添加操作到队列、根据链式标记返回结果”,非链式模式下立即执行操作。具体实现:

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();
};