同步模块模式

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-12-15

模块化:将复杂的系统分解为高内聚、低耦合的模块,使系统开发变的可控、可维护、可拓展,提高模块的复用率。

同步模块模式(SMD: Synchronous Module Definition):请求发出后,无论模块是否存在,立即执行后续的逻辑,实现模块开发中对模块的立即引用。

// 1、模块管理器对象
var F = {};

// 2、定义模块的方法
F.define = function(str, fn) {
  var parts = str.split('.');

  var old, parent;

  // old存储当前模块的祖父模块,parent存储当前模块父模块
  old = parent = this;

  // 如果第一个是模块是模块管理器对象,则移除
  // F.string.trim => string.trim
  if (parts[0] === 'F') {
    parts = parts.slice(1);
  }


  // 屏蔽本身提供的define和module方法
  if (parts[0] === 'define' || parts['0'] === 'module') {
    return;
  }

  for(var i = 0, len = parts.length; i < len; i++) {
    // 当前模块不存在父模块中
    if (typeof parent[parts[i]] === 'undefined') {
      parent[parts[i]] = {};
    }

    old = parent; // 缓存当前父模块为下一轮循环中的祖父模块
    parent = parent[parts[i]]; // 缓存当前模块为下一轮循环中的父模块

    // 如果str为‘sting.trim’ 第一次循环 F.string = {};
    // 第二次循环 F.string.trim = {};
    // 此时old为string这个对象,parents为trim这个对象
  }

  // 如果给定模块方法,则直接调用该模块方法
  if (fn) {
    // old为倒数第二个层级的模块
    // parts[parts.length-1] 为最后一个层级的模块
    old[parts[parts.length-1]] = fn();
  }

  return this;
};

// 3、定义模块
// 定义string模块,父模块不能晚于子模块定义,因为子模块定义时若没有父模块会新建一个,若晚于子模块定义,子模块定义声明的父模块会整个被覆盖
F.define('string', function() {
  return {
    call: function() {
      console.log('string call')
    }
  }
})

// 继续定义string.trim方法
F.define('string.trim', function() {
  return function(str) {
    return str.replace(/^\s+|\s+$/g, '');
  }
})

// 测试string模块
var demo = F.string.trim('  abc  ');
console.log(demo); // 'abc'
F.string.call(); // 'string call'


// 定义dom模块
F.define('dom', function() {
  var $ = function(id) {
    $.dom = document.getElementById(id);
    return $;
  }

  $.html = function(html) {
    // 有参数代表setter,没参数代表getter
    if (html) {
      this.dom.innerHTML = html;
    } else {
      return this.dom.innerHTML;
    }
  }

  return $;
})

// 测试dom模块
// 测试页面 <div id="test">test</div>
F.dom('test').html(); // 'test'

// 4、调用模块方法 => F.module(['string', 'dom'], () => { ... })
F.module = function() {
  var args = Array.from(arguments);
  // 取最后一个参数作为回调执行函数
  var fn = args.pop();

  // 如果第一个参数是数组,则需要的依赖模块组为第一个参数,否则需要依赖的模块组为整个参数列表(除最后一个外)
  var moduleArr = args[0] && args[0] instanceof Array ? args[0] : args;

  // 需要依赖(调用)的模块列表
  var modules = []; 

  // 遍历参数列表,找到所有需要依赖的模块 
  for (var i = 0, mLen = moduleArr.length; i < mLen; i++) {
    // 举例:moduleArr为 ['string.trim', 'F.dom'];
    if (typeof moduleArr[i] === 'string') {
      // 记录当前模块的父模块为F
      var parent = this;
      // 获取当前模块的路由路径,解析路由,并屏蔽首层模块 F.
      var moduleParts = moduleArr[i].replace(/^F\./, '').split('.');

      // 举例:moduleParts 为 ['string', 'trim']  和 ['dom']
      for (var j = 0, mPartsLen = moduleParts.length; j < mPartsLen; j++) {
        parent = parent[moduleParts[j]] || false;
        // 第一轮 parent = this['string'] = F.string
        // 第二轮 parent = F.string['trim']
      }
      modules.push(parent); // 将最终指向的模块添加到依赖的模块列表
    } else {
      modules.push(moduleArr[i]); // 如果不是路径,将该模块直接添加进列表
    }
  }

  // 收集到所有需要调用的模块列表,回传给调用者
  fn.apply(null, modules);
}

// 5、使用

// 将需要使用的一级模块依次传入
F.module('string', 'dom', function(string, dom) {
  console.log(string.trim('  123  ')); // 123
  console.log(dom('test').html()); // test
})

// 将需要使用的一、二级模块一次传入
F.module('string.trim', 'dom', function(trim, dom) {
  console.log(trim('  456  ')); // 456
  console.log(dom('test').html()); // test
})

// 将需要使用的模块用数组的方式传入
F.module(['string.trim', 'dom'], function(trim, dom) {
  console.log(trim('  456  ')); // 456
  console.log(dom('test').html()); // test
})

// 自定义一个方法模块
var func1 = function(str) {
  console.log('func1 module ' + 'params is ' + str)
}

F.module('string.trim', func1, function(trim, customFn) {
  console.log(trim('  456  ')); // 456
  customFn('abc'); // abc
})

// 自定义一个对象模块
var obj = { name: 'jack', age: ' 18  '}
F.module('string.trim', obj, function(trim, customObj) {
  console.log(trim(customObj.age)); // 18
  console.log(customObj.name); // jack
})