VueX实现原理之ModuleCollection原理和作用

484 阅读4分钟

在Store的构造函数里面,有这么一个实例构建的过程 0FV70H.png 经过这个实例的构建,上图中的options会以下图的方式存储在store._mudules中 0FE7iq.png 0FEoon.png

看出来区别了吗? 层级结构没变,也就是说child还是子集,但是options和child对象的结构有了变化

为了容易说明,下文中所说的普通module对象就是指上文中的options/child,所说实例module就是下图中的包含rawModule、state、child等属性的实例module,实例module是通过new Module(参数为普通module)得到的。

既然提到了,可以先说说Module构造函数了

var Module = function Module (rawModule, runtime) {
  this._children = Object.create(null);
  this._rawModule = rawModule;
  var rawState = rawModule.state;
  this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};

先一张图来看看普通module和实例module之间的关系 0AkHAA.png

可以看到,其实构造module和module的关系就是

  • 前者的属性rawModule的值就是其对应的整个完整的普通module,
  • state的值是其对应的普通module的state属性,
  • 在普通module中,module的子module以键值对的形式存在父级的modules属性中,而在实例module中,每一个子module被构建成实例module,存放在其父级的child属性里
  • 同时,Module还有两个原型方法与这次要说的知识相关的函数: addChild()和通过key来getChild()来设置和获取子级
Module.prototype.addChild = function addChild (key, module) {
  this._children[key] = module;
};
ModuleCollection.prototype.get = function get (path) {
  return path.reduce(function (module, key) {
    return module.getChild(key)
  }, this.root)
};

上面的一系列转换,方便了ModuleCollection的get方法和间接地使接下来installModule方法可以来递归获取深层级的子级(这个方法下一次再说)

了解完Module构造函数之后,就可以回到ModuleCollection了

先看看他初始化的时候,入参就是普通module对象 0AeheU.png

var ModuleCollection = function ModuleCollection (rawRootModule) {
  this.register([], rawRootModule, false);
};
ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
  var this$1 = this;
  
  //无关代码省略

  var newModule = new Module(rawModule, runtime); // 先把普通module变成对应的实例module
  //假如没有path长度,说明是第一次调用,因为第一次在ModuleCollection构造函数中是这样调用的
  //this.register([], rawRootModule, false);
  //而从第二层级开始,path参数就会插入子级的key
  //this$1.register(path.concat(key), rawChildModule, runtime);
  if (path.length === 0) {
    this.root = newModule; 
  } else {
    var parent = this.get(path.slice(0, -1));
    parent.addChild(path[path.length - 1], newModule);
  }

  // register nested modules
  if (rawModule.modules) {
    forEachValue(rawModule.modules, function (rawChildModule, key) {
      this$1.register(path.concat(key), rawChildModule, runtime);
    });
  }
};

在第一次调用register函数的的时候

this.register([], options, false);

options作为参数传入,options会作为参数创造出一个实例module(假设命名A),在里面因为path的长度为0,所以this.root就是A. options的module属性有子module child,所以要第二次register

this$1.register(path.concat(key), rawChildModule, runtime);

实参是这样的

this$1.register(['child'], child, false);

因为path长度不为0,所以需要执行以下代码

var newModule = new Module(rawModule, runtime);
var parent = this.get(path.slice(0, -1));
parent.addChild(path[path.length - 1], newModule);

实参是这样的

var newModule = new Module(child, false);
var parent = this.get([]);
parent.addChild('child', newModule);

假如说普通module child还有modules属性,里面有一个 key为grandChild的普通module(B),就会第三次执行register函数

this$1.register(['child''grandChild'], B, false);

path不为空,所以也是会执行以下函数

var newModule = new Module(B, false);
var parent = this.get(['child']);
parent.addChild('grandChild', newModule);

一直到这一层级的普通module没有modules属性或者该属性里面没有内容,递归便结束了,这个new ModuleCollection的构造过程也结束了

上面有一个get函数,其实就是运用了Module构造函数的getChild方法

ModuleCollection.prototype.get = function get (path) {
  return path.reduce(function (module, key) {
    return module.getChild(key)
  }, this.root)
};

上面就是this.get函数,入参是path,举个例子

  • 假如path为[],长度为0,reduce会直接返回this.root
  • 假如path为['child'],长度为0,reduce会直接返回this.root,然后通过this.root.getChild('child'),得到this.root['child']
  • 假如path为['child','grandChild'],reduce会在上面返回的this.root['child'],通过this.root['child'].getChild('grandChild')获得this.root['child']['grendChild']
  • 假如path为['child','grandChild','xxx'],同理会返回this.root['child']['grandChild']['xxx']
  • 依次类推..

和上面的get方法类似实现原理的还有getNameSpace这个方法

ModuleCollection.prototype.getNamespace = function getNamespace (path) {
  var module = this.root;
  return path.reduce(function (namespace, key) {
    module = module.getChild(key);
    return namespace + (module.namespaced ? key + '/' : '')
  }, '')
};

先看看方法执行的效果 0AdOXT.png

  • 可以看到 这个普通module的namespaced为falsy的话,那getNamespace返回的值讲和它的父级是一样的,但是这个返回的值在module.namespaced为falsy的时候根本不会被使用,所以有相同的也无所谓了
// module.namespaced为falsy的时候,
// namespace只会拼接上一次reduce返回的namespace'',
// namespace+''===namespce 

return namespace + (module.namespaced ? key + '/' : '')

举个例子上图中的child/grand2xChild/是怎么得到的

this.getNamespace(['child','grandChild','grandChildChild']) 入参path是['child','grandChild','grandChildChild'] path.reduce第一次

module = module.getChild(key); // module为this.root['child'],
return namespace + (module.namespaced ? key + '/' : '')  /这个module的namespaced为true,key是'child',所以namespace = ''+'child'+'/'('child/'),这个namesapce将在第二次reduce中使用

path.reduce第二次

module = module.getChild(key);//module为this.root['child']['grandChild'],
return namespace + (module.namespaced ? key + '/' : '')//,这个module的namespaced为undefined,所以namespace = 'child/' + '' ,还是'child/',这个namesapce将在第三次reduce中使用

path.reduce第三次

module = module.getChild(key);//module为this.root['child']['grandChild']['grandx2child'],
return namespace + (module.namespaced ? key + '/' : '')//,这个module的namespaced为true,key是grandx2child,所以namespace = 'child/'+'grandx2child'+'/' ,('child/grandx2child/')

好了,最后看不明白的可以去瞅瞅reduce这个函数了解一下,再看不懂那就是我的问题了。。

总结来说ModuleCollection做了以下几件事情

  1. 通过register方法,转换普通module为Module的实例,并按照普通module的父子层级关系建立this.root对象
  2. 注册了get和getNameSpace方法。(当然还有其他的一些方法,但和本文关系不大)