模块

JavaScript 中,模块只不过是基于函数某些特性的代码组织方式。

在《你不知道的 JavaScript》中,给出了模块模式因具备的两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

从中我们可以看到一个比较重要的一点,从函数调用所返回的只有数据属性而没有闭包函数的对象并不是真正的模块。

你看?,理解闭包的重要性再次体现出来了。

从以上要求的两点来看,只要满足相应的条件,我们很容易写出一个模块。

const userModule = ((name = 'module') => {
  let id = 1,moduleName = name;
  const sayName = () => {
      console.log('moduleName: %s', moduleName);
  };
  const sayId = () => {
    console.log('id: %s', id);
  };
  const changeName = value => {
      moduleName = value;
  };
  const changePublicAPI = () => {
    publicAPI.sayIdentification = sayId
  };
  const publicAPI = {
    sayIdentification: sayName,
    changeName,
    changePublicAPI,
  }
  return publicAPI;
})();

以上在满足两个必要的基础上转换成了 IIFE(立即执行函数表达式)。同时可以看出,基于函数的模块可以在运行时通过内部保留着公共 API 对象的引用,从而对模块实例进行修改。

模块机制

模块的出现也是为了能够提高代码的复用率,方便代码管理。复用模块,自然会出现模块依赖的问题,所以说我们需要一个管理模块依赖的模块。

const moduleManage = (() => {
  let modules = {};
  const define = (name, deps, module) => {
    deps = deps.map(item => modules[item])
    modules[name] = module(...deps);
  };
  const exports = (name) => {
    return modules[name];
  }
  return {
    define,
    exports,
  }
})();

moduleManage.define('a', [], () => {
  const sayName = name => {
    console.log('name: %s', name);
  };
  return {
    sayName,
  }
});
moduleManage.define('b', ['a'], (a) => {
  let name = 'b';
  const sayName = () => {
    a.sayName(name)
  };
  return {
    sayName,
  }
});

var b = moduleManage.exports('b');
b.sayName();

模块依赖管理器也依然是个模块,这里的实现其实很简单。modules[name] = module(...deps),使用 modules 缓存各个模块,对于依赖模块的模块,则把依赖作为参数使用。

规范

CommonJS 规范服务于服务端,同步阻塞,在写法风格上是依赖就近。但是在浏览器上,CommonJS 就不好使了,浏览器需要从服务器请求数据,下载完成后才会有下一步的执行。如果采用 CommonJS 的同步方式,指不定什么时候文件才会下载完成。

为了推广到浏览器上,AMD 规范采用异步方式加载模块。先异步加载模块,加载完成后就可以在回调中使用依赖模块了。这样就保证了在使用依赖时,依赖已经加载完成。AMD 规范是早早地下载,早早地执行,在回调里 require 的是依赖的引用。在写法风格上是依赖前置,这种风格已经不同于 CommonJS 了。还有,这里早早地执行会带来一个问题,如果存在某个依赖某些条件不成立,导致没有用上。那么,这里的早早地执行岂不是多此一举了?

CMD 规范是 sea.js 推崇的规范,它采用的也是异步加载模块的方式,只是在依赖模块的执行时机上有所不同。在写法风格上,又回归到 CommonJS,依赖就近。sea.js 是早早地下载,延迟执行。

到了 ES6,终于从语法上支持模块化了,ES6 模块是编译时加载,使得在编译时就能确定模块的依赖关系,而且在将来服务器和浏览器都会支持 ES6 的模块化方案。


至棣
109 声望3 粉丝