学习笔记之JS模块化

101 阅读5分钟

一 模块化

  1. 每个文件都要有自己的变量作用域,两个模块之间的内部变量不会产生冲突;
  2. 不同模块之间保留相互导入和导出的方法,模块间能够相互通信.模块的执行与加载遵循一定的对方,能保证彼此之间的依赖关系;

二 JS模块化产生背景

  1. 在web开发早期,为了团队协作和代码维护的方便,许多开发者会将JavaScript代码分开写在不同的文件里面,然后,通过过个script标签加载他们.
  2. 虽然每个代码块处在不同的文件中,但最终所有js变量还是会处在一个同一个全局作用域下,这个时候就需要格外注意作用域变量提升所带来的问题.
  3. 如果两个代码块都声明相同的变量并为其赋值,则这个变量名的最终结果,就是按照加载顺序进行覆盖替换后的最终结果.
  4. 如果两个代码块存在依赖关系,我们处理好模块的加载顺序
  5. 我了解决这一系列问题:我们需要将这些脚本文件``模块化.

三 JS模块化规范

CommonJS

  • node.js是基于V8引擎,事件驱动I/O的服务端JS运行环境.

  • 它实现了一套CommonJS的模块化规范.

  • 在CommonJS规范里,每个JS文件就是一个模块,每个模块内部可以利用require函数和module.exports地偶像来对模块进行导入和导出;

  • CommonJS的包含了模块化的那些特征?

有处理模块变量作用域的能力 有导入导出模块的方式,能够处理基本的依赖关系 保证了模块单例

AMD

  • 为了满足web开发的需要,如果在web端也使用同步加载,那么页面在解析脚本文件的过程中可能使页面暂停响应.
  • 所以,web更适合使用AMD规范(Asynchronous module definition),也就是异步模块定义.
// index.js
require(['moduleA','moduleB'],function(moduleA,moduleB) {
  console.log(moduleB);
})

// moduleA.js
define(function(require) {
  var m = require('moduleB');
  setTimeout(()=>console.log(m),1000)
})

// moduleB.js
define(function(require) {
  var m = new Date().getTime();
  return m;
})
  • AMD默认异步,在回调函数中定义模块内容,相对来说使用就会麻烦些;
  • AMD模块不能直接在node端运行,require函数配合在浏览器中加载的require.js这类AMD库才能使用;

UMD

  1. UMD(Universal Module Definition)作为一种同构的模块化解决方案,能够同时兼容AMD和Commonjs语法.
  2. 同构,也就是说我们使用同一套代码就可以运行在两个不同的环境中,不需要分别维护多套代码;
  3. UMD通过检测当前环境的模块化规范,将模块内容进行输出,如果没有检测树规范则将模块内容挂载到全局对象上.
/* 这是一个立即执行函数(this=window or global,factory = moduleContent) */
(function(self, factory) {
  /* 判断环境暴露的module接口是否为对象,module.exports 接口是否为对象,如果是则为CommonJS规范的环境 */
 if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = factory(); // 将工厂函数复制给module.exports
  /* 判断环境暴露的define接口是否为函数,且define.amd是否存在,如果是,则为AMD规范环境 */
 } else if (typeof define === 'function' && define.amd) {
 define(factory) // 将工厂函数作为参数调用define函数
 /* 如果什么都不是,直接挂在全局变量umdModule上 */
 } else {
 self.umdModule = factory(); 
 }
}(this, function() {
 return function() {
 return Math.random();
 }
}));

ESModule规范

  • CommonJS规范和AMD规范都是:
  1. 语言上层的宿主环境中定义的模块化规范;
  2. 相互之间不能共用模块;例如在nodejs中不能直接使用AMD模块,在浏览器中不能世界使用CommonJS模块
  • es6之后,js有了语言层面上的模块化规范,也就是esmodule规范,我们可以通过import和export两个关键词来对模块进行导入和导出.

  • 所以与CommonJS和AMD最大的区别在于:

esmodule是由js解释器实现的,而后两者是由宿主环境中运行时实现的. esmodule的导入实际上是在语法层面上新增了一个语句,而AMD和CommonJS加载模块实际上是调用了require函数.

  • 宿主环境与js解析器之间的关系
node.js         浏览器(w3c规范)  小程序
global          document        wx
process         window          swan
   ↑               ↑              ↑
_____________________________________
            JS Core(ecmascript规范)
            var a = 123;
 function test() { console.log(true) }
  • 类似于setTimeout和console这样的API大部分并不是JSCore层面实现的,只不过所有运行环境都实现了类似的效果
  • setTimeout在ES7之后才进入了JScore层面

四 JS模块化展望

  1. 通过目前的分析我们可以看出,使用ESModule的模块明显符合JS开发的历史进程

  2. 因为任何一个支持JS的环境,随着对应解释器的升级,最终一定会支持ESModule的标准

  3. 但是web端受限制于用户使用的浏览器版本,我们不能随心所欲的随时使用js的最新特性.

  4. 为了能让我们代码也能运行在用户的老浏览器中,社区涌现出越来越多的工具,它们能静态将高版本规范的代码编译为低版本规范代码, 最为大家所熟知的就是babel;

  5. babel把JS Core中最高版本规范的语法,也能按照相同语义在静态阶段转化为低版本规范的语法,这样即使早期的浏览器,他们内置的js解析器也能看懂;