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__链向上查找:- 如果
wrapper是LazyWrapper实例,在LazyWrapper.prototype中查找map方法。 - 如果
LazyWrapper.prototype中没有,继续在baseLodash.prototype中查找。 - 如果
wrapper是LodashWrapper实例,在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();
};