勿忘初心

个人签名

530篇博客

NEJ-Dispatcher剖析(二) 调度原理

勿忘初心2018-10-26 11:31

此文已由作者杨介正授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


1 背景
1.1 什么是模块
  JS中的模块根据规范分为两种,分别是符合CommonJs规范的模块和符合AMD规范的模块。前者规定每个js文件为一个模块,通过exports对外输出接口,其余变量对外不可见,适合写服务端模块。后者通过define(name,dependencies,factory)函数定义模块,name为模块名,dependencies为模块依赖,factory为模块制造工厂返回模块本体,适合写客户端模块。

1.2 什么是模块调度

  模块调度就是根据特定的要求加载特定的模块,这里的“特定的要求”大多指的就是location的hash值(因为有历史记录,方便浏览器前进后退);这里的“加载特定的模块”指得是通过http请求获取模块本体并执行模块内部相应功能。通过解析hash值的变化,来加载不同的模块。

2 NEJ模块调度原理
2.1 dispatcher中的模块
  dispatcher中的模块是符合AMD标准的模块,分为公有模块和私有模块两类。公有模块是可以被独立调度的,私有模块只能寄生在公有模块中,不能独立被调 本文用ModuleAbstract表示标准模块,一个标准的模块大致结构如下图
  这里着重解释下umi和umi管理器。umi(unique module identify),模块唯一标识符,即模块的ID。公有模块的umi等于调度此模块的hash值,私有模块的umi以/?/开头,如/?/book/component/。umi manager,即umi管理器。dispatcher内置public umi manager和private umi manager,分别用于管理公共模块和私有模块。默认情况下,公共模块由public umi manager管理,私有模块由private umi manager管理。当然,你也可以为模块指定umi manager,通过给注册信息添加gid值。这个在私有模块的注册中尤为有用,因为默认情况下,主模块的私有模块会一次性全部显示出来。如果你希望一次只 显示 一个私有模块并实现它们之间的切换,则需要将私有模块分配在同一个umi manager中。public umi manager继承至private umi manager,拥有更强大的功能。dispatcher的模块调度是通过umi manager调度umi实现的:dispatcher根据各个模块注册信息中的umi,生成一颗node tree,tree结构用于标识模块间的层级关系,node节点保存模块的具体信息。比如注册模块信息为/m/book/: moduleBook,dispatcher会生成如下的node tree结构


  umi manager 调度umi是指显示、隐藏、刷新node tree里的某一路,显示或者刷新的执行顺序为祖先节点到子节点,子节点的操作必须是在父节点已完成对应操作的前提下,而隐藏的执行顺序是子节点到祖先节点。比如显示/m/book/,顺序为/ -> m -> book -> /。public umi manager和private umi manager的调度策略是不同的。public umi manager调度过程是隐藏公共节点到源节点,刷新根节点到公共节点,最后才是显示公共节点到目标节点。在最后的显示阶段,通过遍历根节点到目标节点的线路,确保整条线路上node对应的模块已经准备就绪,才会继续执行。如果有节点对应的module存在,但只是一个html文件地址,则从服务端获取之。在获取结束时,public umi manager会重新遍历根节点到目标节点的线路,如此反复,直到所有模块都准备就绪。public umi manager这种调度策略可以保证线路上的模块都能按结构显示,但执行效率比较低。私有模块分组能实现单独显示与切换,原因就在于自定义的umi manager也是public umi manager 类型的,用的是相同的调度策略。虽然主模块所有的私有模块仍然会被同时加载过来,但由于一时间只有一个目标节点,因此只会显示出排在最末位的私有模块。private umi manager的调度策略较为简单,它只负责显示或者刷新目标节点,也不会去查询整条线路上的模块是否准备就绪,执行效率较高,适合私有模块的调度。

2.2 基本调度过程
  1 dispatcher注册所有模块,各个模块的注册信息包括模块的umi、module、title、clazz、composite。module可以是一个标准的ModuleAbstract,也可以是一个html文件的地址。如果module是个html的地址,此html中必须包含重新注册此模块的js代码,并且所注册的模块必须是一个标准的ModuleAbstract。title是模块加载时document对应的title;clazz是模块加载body的类名;composite是模块对应的私有模块。
  2 dispatcher会根据各个模块注册信息中的umi,生成一颗node tree。
  3 history通知dispatcher有hash值发生变化。
  4 dispatcher以hash值为umi,通知public umi manager调度相应umi
  5 public umi manager根据node tree上的线路完成相应操作。
  6 如果主模块有私有模块,则此主模块会通知private umi manager去调度私有模块。
  7 private umi manager根据node tree 上的线路完成相应操作。

  下面以一个实际例子来说明上述过程:
  1 注册信息
modules:{
	'/m/book/': {//public module
		module: 'module/book/index.html',
		composite: {
			cp: '/?/cp/'
		}
	},
       '/m/food/': 'module/food/index.html',//public module
	'/?/cp/': 'module/cp/index.html'//private module
}
  2 dispatcher会根据注册信息生成如下结构


   3 history通知dispatcher有hash值变为/m/book/,dispatcher开始调度。NEJ-Dispatcher剖析(一) History

  这里要注意的是,只有节点上有对应模块,node的显示、隐藏、刷新操作才会去触发对应模块的__onShow、__onHide、__onRefresh方法。

  4 history通知dispatcher hash值切换为/m/food/

2.3 模块间的通信
  模块间的通信是通过相互之间传递消息实现的,而消息机制内部是通过虚拟事件(addEvent/delEvent/dispatchEvent)完成的。消息机制包括发送接收和发布订阅两中方式,第一种方式是直接给目标模块发送onmessage事件,目标模块在onmessage中接收处理消息;第二中方式是通过在dispatcher中注册事件来完成,事件名的格式为umi:type。比如模块A订阅模块B的消息B:change,即在dispatcher中注册一个事件B:change并添加模块A在订阅到此消息时的处理逻辑。模块B在发布消息change时,则dispatcher触发事件B:change。

3 总
  JS中的模块根据规范分为两种,分别是符合CommonJs规范的模块和符合AMD规范的模块。前者适合服务端(nodejs)开发,后者适合客户端(web browser)开发。模块调度是指根据特定的要求(hash)加载特定的模块并执行模块内部相应方法。Nej模块调度系统-dispatcher中的模块是复合amd标准的ModuleAbstract,用umi唯一标识。模块的调度通过umi管理器调度umi实现,而依据umi构造的node tree则充当模块调度线路的作用。umi管理器又分为public umi manager和private umi manager,由于它们不同偏重的调度策略,public umi manager可以保证线路的完整性,用于调度公共模块;private umi manager更加高效,用于调度私有模块。模块间的通信是通过相互之间传递消息实现的,而消息机制内部是通过虚拟事件完成的。

网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击