Lodash源码解析一全局架构实现

2,105 阅读5分钟

Lodash源码解析一全局架构实现

抱歉

首先抱歉这一个月来没有更新文章,因为这一个月一直在忙于加班赶项目,以及家庭琐事,所以没有更新,虽如此,但这都是借口,比我忙的人多的是,但很多人还能保持良好的习惯,所以该自责~~~

新的思路

这次不具体分析方法,因为我发现很多人对Lodash的底层实现方式并不是很了解(当然我也不太了解...),就好比很多前端小伙伴vue和react用的贼溜,但一问实现方式但都模棱两可,知其然而不知其所以然,这很可怕,所以我打算深入研究下其基本逻辑,然后再继续方法的学习。

整体架构

三个模块

Lodash.png

  • 全局变量定义
  • 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这个函数的实现思路

  1. 函数内变量
  2. lodash自定义的方法
  3. 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都可以找到他定的方法,这样特别适合全局去使用,这样对于我们如果要封装一些方法类,提供了很好的思路。