Lodash源码解析一全局架构实现
抱歉
首先抱歉这一个月来没有更新文章,因为这一个月一直在忙于加班赶项目,以及家庭琐事,所以没有更新,虽如此,但这都是借口,比我忙的人多的是,但很多人还能保持良好的习惯,所以该自责~~~
新的思路
这次不具体分析方法,因为我发现很多人对Lodash的底层实现方式并不是很了解(当然我也不太了解...),就好比很多前端小伙伴vue和react用的贼溜,但一问实现方式但都模棱两可,知其然而不知其所以然,这很可怕,所以我打算深入研究下其基本逻辑,然后再继续方法的学习。
整体架构
三个模块
- 全局变量定义
- runInContext函数封装loadsh
- 导出lodash方法,挂载this对象,以及各种环境下暴露方法
全局变量定义模块
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
var undefined;
/** Used as the semantic version number. */
var VERSION = '4.17.21';
/** Used as the size to enable large array optimizations. */
var LARGE_ARRAY_SIZE = 200;
/** Error message constants. */
var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
FUNC_ERROR_TEXT = 'Expected a function',
INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';
/** Used to stand-in for `undefined` hash values. */
var HASH_UNDEFINED = '__lodash_hash_undefined__';
...
...
这个没什么可说的,把需要的基本变量全部定义出来
runInContext函数
var runInContext = (function runInContext(context) {
context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
/** Built-in constructor references. */
var Array = context.Array,
Date = context.Date,
Error = context.Error,
Function = context.Function,
Math = context.Math,
Object = context.Object,
RegExp = context.RegExp,
String = context.String,
TypeError = context.TypeError;
/** Used for built-in method references. */
var arrayProto = Array.prototype,
funcProto = Function.prototype,
objectProto = Object.prototype;
/** Used to detect overreaching core-js shims. */
var coreJsData = context['__core-js_shared__'];
...
...
// Export lodash.
var _ = runInContext();
这里面会var _ = runInContext()
把这个方法把Lodash暴露出去,runInContext
函数把lodash再次封装一次,具体再看一下runInContext
这个函数的实现思路
- 函数内变量
- lodash自定义的方法
- mixin混合到lodash原型链
var runInContext = (
function runInContext(context) {
context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
console.log(context)
/** Built-in constructor references. */
var Array = context.Array,
Date = context.Date,
Error = context.Error,
Function = context.Function,
Math = context.Math,
Object = context.Object,
RegExp = context.RegExp,
String = context.String,
TypeError = context.TypeError;
...
...
//定义lodash的方法
function lodash(value) {
if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
if (value instanceof LodashWrapper) {
return value;
}
if (hasOwnProperty.call(value, '__wrapped__')) {
return wrapperClone(value);
}
}
return new LodashWrapper(value);
}
//lodash里面的定义的方法
/*** Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
*
* @private
* @constructor
* @param {*} value The value to wrap.
*/
function LazyWrapper(value) {
this.__wrapped__ = value;
this.__actions__ = [];
this.__dir__ = 1;
this.__filtered__ = false;
this.__iteratees__ = [];
this.__takeCount__ = MAX_ARRAY_LENGTH;
this.__views__ = [];
}
/**
* Creates a clone of the lazy wrapper object.
*
* @private
* @name clone
* @memberOf LazyWrapper
* @returns {Object} Returns the cloned `LazyWrapper` object.
*/
function lazyClone() {
var result = new LazyWrapper(this.__wrapped__);
result.__actions__ = copyArray(this.__actions__);
result.__dir__ = this.__dir__;
result.__filtered__ = this.__filtered__;
result.__iteratees__ = copyArray(this.__iteratees__);
result.__takeCount__ = this.__takeCount__;
result.__views__ = copyArray(this.__views__);
return result;
}
/**
* Reverses the direction of lazy iteration.
*
* @private
* @name reverse
* @memberOf LazyWrapper
* @returns {Object} Returns the new reversed `LazyWrapper` object.
*/
function lazyReverse() {
if (this.__filtered__) {
var result = new LazyWrapper(this);
result.__dir__ = -1;
result.__filtered__ = true;
} else {
result = this.clone();
result.__dir__ *= -1;
}
return result;
}
...
...
// 把定义好的方法挂载到lodash这个实体类上
// Add methods that return wrapped values in chain sequences.
lodash.after = after;
lodash.ary = ary;
lodash.assign = assign;
lodash.assignIn = assignIn;
lodash.assignInWith = assignInWith;
lodash.assignWith = assignWith;
lodash.at = at;
lodash.before = before;
...
...
// 混合方法
mixin(lodash, (function() {
var source = {};
baseForOwn(lodash, function(func, methodName) {
if (!hasOwnProperty.call(lodash.prototype, methodName)) {
source[methodName] = func;
}
});
return source;
}()), { 'chain': false });
...
...
// 原型链挂载方法,最后return 出去
// Add chain sequence methods to the `lodash` wrapper.
lodash.prototype.at = wrapperAt;
lodash.prototype.chain = wrapperChain;
lodash.prototype.commit = wrapperCommit;
lodash.prototype.next = wrapperNext;
lodash.prototype.plant = wrapperPlant;
lodash.prototype.reverse = wrapperReverse;
lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
// Add lazy aliases.
lodash.prototype.first = lodash.prototype.head;
if (symIterator) {
lodash.prototype[symIterator] = wrapperToIterator;
}
return lodash;
定义lodash原方法,以及mixn混合方法
/**
*
*
*
*/
function lodash(value) {
if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { // 第一次调用,这一步是空的可以略过
if (value instanceof LodashWrapper) {
return value;
}
if (hasOwnProperty.call(value, '__wrapped__')) {
return wrapperClone(value);
}
}
return new LodashWrapper(value); //
/*
* 最后返回已定义好的原型对象
* 至于为什么这么搞,为啥不直接定义object,这个就牵扯到原型链的知识,咱们下次展开来讲,这次先理解是返回来一个实例化的对象
*/
}
...
...
function LodashWrapper(value, chainAll) {
this.__wrapped__ = value;
this.__actions__ = [];
this.__chain__ = !!chainAll;
this.__index__ = 0;
this.__values__ = undefined;
}
...
...
// Add methods to `lodash.prototype`.
// 第一次混合,把定义好的方法混合到lodash的原型中
mixin(lodash, lodash);
...
...
// 第二次混合,对比之前混入的方法,把新定义的方法再次混合到lodash的原型链上
// 这里有个疑问,为什么不一次引进,要来第二次混合,这里先不过多解读,下次继续
mixin(lodash, (function() {
var source = {};
baseForOwn(lodash, function(func, methodName) {
if (!hasOwnProperty.call(lodash.prototype, methodName)) {
source[methodName] = func;
}
});
console.log(source)
return source;
}()), { 'chain': false });
function mixin(object, source, options) {
var props = keys(source), // 定义的方法数量集合
methodNames = baseFunctions(source, props); // 定义的各个方法名字集合
if (options == null && !(isObject(source) && (methodNames.length || !props.length))) {
//初始化不会走这个判断,可忽略
options = source;
source = object;
object = this;
methodNames = baseFunctions(source, keys(source));
}
var chain = !(isObject(options) && 'chain' in options) || !!options.chain, // options传chain(是否链式操作)的时候返回true 初始化返回true
isFunc = isFunction(object); // 因为指定的是lodash函数,所以是true
arrayEach(methodNames, function(methodName) {
var func = source[methodName];
object[methodName] = func; // 循环方法名,然后把方法名注入到lodash对象中
if (isFunc) {
// 循环方法名,然后把方法名注入到lodash的原型链中,这里实现方法到下节着重介绍
object.prototype[methodName] = function() {
var chainAll = this.__chain__;
if (chain || chainAll) {
var result = object(this.__wrapped__),
actions = result.__actions__ = copyArray(this.__actions__);
actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
result.__chain__ = chainAll;
return result;
}
return func.apply(object, arrayPush([this.value()], arguments));
};
}
});
return object;
}
loadsh各种挂载暴露方法
// Export lodash.
var _ = runInContext();
// Some AMD build optimizers, like r.js, check for condition patterns like:
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
// Expose Lodash on the global object to prevent errors when Lodash is
// loaded by a script tag in the presence of an AMD loader.
// See http://requirejs.org/docs/errors.html#mismatch for more details.
// Use `_.noConflict` to remove Lodash from the global object.
root._ = _;
// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
define(function() {
return _;
});
}
// Check for `exports` after `define` in case a build optimizer adds it.
else if (freeModule) {
// Export for Node.js.
(freeModule.exports = _)._ = _;
// Export for CommonJS support.
freeExports._ = _;
}
else {
// Export to the global object.
root._ = _;
}
/*
这里的逻辑主要是判断当前环境,分三种情况:
1.部分AMD模式暴露Lodash到root(window对象),define导出Lodash
2.有exports方法, 如果是node环境和common.js环境导出Lodash
3.无环境导出直接挂载在到this里面(windows对象),所以我们使用时候用this._fun和直接用_.fuc一样的效果
*/
}.call(this));
总结
到此,lodash的整体思路已经很清晰了,实际上整体目的是把定义的好的各种方法,挂载到Lodash这个实体类上,在这个实体类里面,又把各个方法放进了其原型中,这样做的好处是,无论你是直接调用lodash,还是调用this和window都可以找到他定的方法,这样特别适合全局去使用,这样对于我们如果要封装一些方法类,提供了很好的思路。