模块化fis.js源码分析

2017-11-01  本文已影响63人  天外来人帅

前言

正文

基于AMD和CMD等相关知识,简要分析下fis.js模块。先简单分析下fis.js结构,再分析下页面结构,最后分析下fis.js执行逻辑, 依照这样的三部曲,开始下面的旅程。

简单分析下fis.js结构

下面是fis.js源码的主体结构示意图,分为三部分:F对象、Module对象、工具函数。

(function () {
    //定义全局对象F
    var F = {
        'version': '2.0.0',
        'debug': false
    }
    //定义一些工具函数
    
    //定义模块类
    function Module (path, name) {
    }
    Module.prototype = {};
    
    //暴露接口module: 声明一个模块
    F.module = function (name, fn, deps) {};
  
    //暴露接口use: 使用一个模块或多个模块
    F.use = function(names, fn) {};

    window.F = F;
})();

简单分析下页面结构

以下是以贴吧智能版为例,页面结构简要图:

<html>
    <head></head>
    <body>
        <script src="//zhangshuai.static.tieba.otp.baidu.com/??tb/mobile/sglobal/lib/lib_1e478fb.js,tb/mobile/sglobal/lib/extend_3034d8c.js"></script>
        <script src="//zhangshuai.static.tieba.otp.baidu.com/tb/mobile/sglobal/lib/common_feae225.js"></script>
        <script src="//zhangshuai.static.tieba.otp.baidu.com/??/tb/mobile/app_starter_eec059c.js,/tb/mobile/aa_dff854c.js,/tb/mobile/app_open_885d443.js,/tb/mobile/ua_device_11a1c37.js,/tb/mobile/slider_6d37d9a.js,/tb/mobile/pic_free_mode_adapter_39a74e5.js,/tb/mobile/app_starter_conf_9958175.js,/tb/mobile/clouda_push_6bbb96d.js,/tb/mobile/slide_image_8c6ae49.js,/tb/mobile/slider_main_470314e.js,/tb/mobile/slider_event_aaaa509.js,/tb/mobile/slider_header_cada614.js,/tb/mobile/slider_abstract_447695c.js"></script>
        <div>
            ....
        </div>
        <script>
            F.use(['xxx'], function(obj){
                ...
            });
        </script>
        <script>
            F.use(['yyy'], function(obj){
                ...
            });
        </script>
        ...
    </body>
</html>

第三个script 中js形如:

  F.module('alert', function() {...},[]);
  F.module('confirm', function(){...},[]);
  F.module('dialog', function(){...},[]);
  ...
执行fis.js:

加载执行fis.js, 将F对象暴露给全局。

window.F = F
image.png
暴露出的API:

F.use: 指定一个或多个模块名,待模块加载完成后执行回调函数,并将模块对象依次传递给函数做参数
F.module(name,function(require, exports){},[]): 声明一个模块。模块定义函数,有两个参数,分别为require, exports。require是一个函数,用来引用其他模块;exports是一个对象,模块函数最终将模块的api挂载到exports对象上,作为模块对外输出唯一对象。

fis.js 运行流程

接下来分析下fis.js 运行流程。以贴吧智能版为例:分为两部分,第一部分是:模块的定义阶段(F.module),第二部分是:模块的调用阶段(F.use)。

模块标记对象:
Module.lazyLoadPaths = {name: true}; //F.module 时标记,在lazyLoad() 后逐一删除掉。
Module.loadedPaths = {path: true}; //已经下载完成的js文件
Module.loadingPaths = {path: true}; //正在下载的   由true -> false
Module.initingPaths = {path: true}; //初始化进行中 由true -> false
Module.requiredPaths = {name: true}; //存放依赖模块 
模块定义阶段:
  // 模块类:
  function Module(path, name){
      //模块名,在define时指定
      this.name = name;
      //模块js文件全路径
      this.path = path;
      //模块函数体
      this.fn = null;
      //模块对象
      this.exports = {};
      this._loaded = false;
      //完成后需要触发的函数
      this._requiredStack = [];
      this._readyStack = [];
      //保存实例,用于单实例判断
      Module.cache[this.name] = this;
  }
  Module.prototype = {
    ...
  };

  //根据名称和路径获取模块实例
  function get(name, path) {
      if (Module.cache[name]) {
          return Module.cache[name];
      }
      return new Module(path, name);
  }

  // F对象
  F = {};

  //模块定义接口
  F.module = function(name, fn, deps){
      var mod = get(name); //获取模块对象,若不存,则返回new Module(name, path);
      mod.fn = fn;
      mod.deps = deps || [];
      if (Module.requiredPaths[name]) {
          mod.define();
      } else {
          Module.lazyLoadPaths[name] = true;
      }  
}

这一阶段做的工作:

执行完之后:
Module.cache(存放模块实例) 保存的内容 形如:

image.png

Module.lazyLoadPaths 保存的内容 形如:

image.png
模块调用阶段:

分析两种情况: 一种是模块预加载了,另一种是模块未被预加载。

  // 指定一个或多个模块名,待模块加载完成后执行回调函数,并将模块对象依次传递给函数作为参数。
  F.use = function(names, fn) {
      if(typeof names === 'string') {
          name = [names]; //names 数组化,统一管理。name 作为Module 实例对象的唯一id。
      }
      forEach(names, function(name, i){
          var mod = get(name);  //get 函数,从Module.cache中获取相应模块,如果没有 返回一个新的new Module();
          mod.ready(function(){  //第一次执行ready
              args[i] = mod.exports;
              if(fn){
                  fn.apply(null,args); //编译阶段,执行F.use 中的回调函数。
              }
         });
         mod.lazyLoad();  
     }
  }
  function Module(path, name) {
        // 模块名,在define时指定
        this.name = name;
        // 模块js文件全路径
        this.path = path;
        // 模块函数
        this.fn = null;
        // 模块对象
        this.exports = {};
        // 包括依赖是否都下载完成
        this._loaded = false;
        // 完成后需要触发的函数
        this._requiredStack = [];
        this._readyStack = [];
        // 保存实例,用于单实例判断
        Module.cache[this.name] = this;
  }
  Module.prototype = {
      // 在此函数中,会调用mod中存储的fn, 也就是调用模块主体函数,抛出exports 保存在mod.exports
      // 若遇到require,会执行require函数,再次调用此函数
      init: function() {
          if (!this._inited) {
              this._inited = true;
              if (!this.fn) {
                  throw new Error('Module "' + this.name + '" not found!');
              }
              var result;
              Module.initingPaths[this.name] = true;
              if (result = this.fn.call(null, require, this.exports)) {
                  this.exports = result;
              }
              Module.initingPaths[this.name] = false;
          }
      },
      // 根据传入参数不同,控制流程。
      ready: function(fn, isRequired) {
          var stack = isRequired ? this._requireStack : this._readyStack;
          if (fn) {
              stack.push(fn);
          }else {
              this._loaded = true;
              Module.loadedPaths[this.path] = true;
              delete Module.loadingPaths[this.path];
              this.triggerStack();
          }
      },
      //根据模块存在的状态,执行不同的函数。
      lazyLoad: function() {
          var name = this.name,
              path = this.path;
          if (Module.lazyLoadPaths[name]) {
              this.define();
              delete Module.lazyLoadPaths[name];
          } else {
              if (Module.loadedPaths[path]) {
                  this.triggerStack();
              } else if (!Module.loadingPaths[path]) {
                  Module.requiredPaths[this.name] = true;
                  this.load();
              }
          }
      },
      //调用分析依赖函数,若存在依赖,则执行mod.ready(fn, true), 否则执行mod.ready();
      define: function() {
          var _this = this,
              deps = this.deps,
              depPaths = [];
          deps = removeCyclicDeps(_this.path, this.deps);
          if (deps.length) {
              Module.loadingPaths[this.path] = true;
              forEach(deps, function(d) {
                  var mod = get(d);
                  depPaths.push(mod.path);
              });
              forEach(deps, function(d) {
                  var mod = get(d);
                  mod.ready(function() {
                      if (isPathsLoaded(depPaths)) {
                          _this.ready();
                      }
                  }, true);
                  mod.lazyLoad();
              });
          } else {
              this.ready();
          }
      },
      // 在此函数中,统一处理回调函数。先调用mod.init() 
      // 再调用mod.ready 传过来的回调函数,此回调函数中调用F.use中的回调函数。
      triggerStack: function() {
          if (this._readyStack.length > 0) {
              this.init();
              forEach(this._readyStack, function(func) {
                  if (!func.doing) {
                      func.doing = true;
                      func();
                  }
              });
              this._readyStack = [];
          }
          if(this._requiredStack.length > 0) {
              forEach(this._readyStack, function(func){
                  if(!func.doing){
                      func.doing = true;
                      func();
                  }
              });
              this._requiredStack = [];
          }
      }
    }

    //实现模块的require方法
    function require(name) {
        var mod = get(name);
        if (!Module.initingPaths[name]) { //清除循环依赖
            mod.init();
        }
        return mod.exports;
    }

优先把页面所依赖的模块,分析打包到js文件中。

总结

循环依赖分析:

循环依赖说明:
例如: 假设有以下三个模块A,B,C
A -> B
B -> C
C -> A

依赖关系如下 a -> [b -> [c -> [a]]]
处理c模块的依赖关系的时候,调用 removeCyclicDeps(c, [a]) 返回return [];

依赖分析源代码

    function removeCyclicDeps(uri, deps) {
        return filter(deps, function(dep) {
            return !Module.loadingPaths[dep] || !isCyclicWaiting(Module.cache[dep], uri, []);
        });
    }

    function isCyclicWaiting(mod, uri, track) {
        if (!mod || mod._loaded)
            return false;
        track.push(mod.name);
        var deps = mod.deps || [];
        if (deps.length) {
            if (indexOf(deps, uri) > -1) {
                return true;
            } else {
                for (var i = 0; i < deps.length; i++) {
                    if (indexOf(track, deps[i]) < 0 && isCyclicWaiting(Module.cache[deps[i]], uri, track)) {
                        return true;
                    }
                }
                return false;
            }
        }
        return false;
    }
上一篇下一篇

猜你喜欢

热点阅读