模块化

Posted by CodingWithAlice on March 17, 2020

模块化

本文主要关注点:AMD/CMD 差异;CommonJS/ES6模块化 差异;4种模块化规范的进步,解决的问题

模块化的优点:避免命名冲突;更好的分离,按需加载;更高复用性;高可维护性;

总结:

1、CommonJS 规范主要用于② 服务端编程;①加载模块是同步,不适合在浏览器环境,存在 阻塞加载;③输出一个值的拷贝;④运行时加载;

浏览器资源是异步加载的,因此有了AMD、CMD解决方案。

2、AMD/RequireJS 规范在浏览器环境中 ① 异步加载模块;而且可以②并行加载多个模块;③依赖前置,④加载模块后直接执行,无法保证执行顺序

3、CMD/SeaJS 规范与AMD规范很相似,都用于浏览器编程,①异步;②依赖就近,③加载后直到调用才按需执行

4、ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器 通用的模块解决方案 - ①异步,②输出的是值的引用,③编译时输出(静态解析时生成)


一、前景提要 - 模块化

  • 模块化的优点
    • 避免命名冲突(减少命名空间污染)
    • 更好的分离, 按需加载
    • 更高复用性
    • 高可维护性

二、AMD/CMD 的区别

1、AMD推崇 依赖前置,在定义模块的时候就要声明其依赖的模块 2、CMD推崇 就近依赖,只有在用到某个模块的时候再去 require

最大的区别:对依赖模块的 执行时机处理不同,注意不是加载的时机或者加载方式不同(都是异步加载)

// AMD - 依赖必须一开始就写好
define(['./a', './b'], function(a, b) {
    a.doSth();
    b.doSth();
})
// CMD - 依赖可以就近书写
define(function(require, exports, module) {
    let a = require('./a');
    a.doSth();
    // ...
    let b = require('./b');
    b.doSth();
})
  AMD异步模块定义 CMD通用模块定义
工具库 RequireJS SeaJS
依赖 依赖前置,js直接找到依赖模块,立即加载 就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,以性能换便利
执行时机 加载模块完成后直接执行,所有模块都加载执行完后会进入 require 的回调函数,执行主逻辑,依赖模块的 执行顺序和书写顺序不一定一致 加载完某个依赖模块后并不执行,在所有依赖模块加载完成后进入主逻辑,遇到 require 语句的时候才执行对应的模块,模块的 执行顺序和书写顺序是完全一致

三、CommonJS 和 ES6 区别 - 代码部分在详解

  CommonJS 模块 ES6 模块 import
  require exports import,export,default,as,from
  require 需要代码同步执行 - 不适合浏览器端 异步
输出 输出的是一个值的拷贝
(一旦输出一个值,模块内部的变化就影响不到这个值)
输出的是值的引用
(动态引用,脚本执行时,再根据引用,到模块里面取值,若原始值变了,import 加载的值也会跟着变)
时机 运行时加载,加载的是 整个模块 - 所有接口,只有在 脚本运行 完才会生成 编译时输出 接口,可以单独加载某个接口,在代码 静态解析 阶段就会生成
加载原理 一个模块就是一个脚本,require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象
以后需要用到这个模块的时候,就会到 exports 属性上面取值。也就是说,不会再次执行该模块,而是到 缓存 之中取值,只会在第一次加载时运行一次
静态化

四、4个模块化规范的详解

1/4、CommonJS

2009年 Nodejs 发布,采用 CommonJS 模块规范。

  • 特点 1、每个文件都是一个模块实例,代码运行在 模块作用域,不会污染全局作用域。

    2、文件内通过 require 对象引入指定模块,通过 exports 对象来向外暴漏API,文件内定义的变量、函数,都是 私有 的,对其他文件不可见

    3、每个模块 加载一次之后就会被缓存

    4、所有文件加载均是 同步 完成,加载的顺序,按照其在代码中出现的顺序

    5、模块输出的是一个 值的拷贝,模块内部的变化不会影响该值

  • 缺点

    1、发送多个请求,模块 同步加载,资源消耗和等待时间 ,适用于 服务器编程

    2、引入的 js 文件顺序不能搞错,否则会报错

  • 基本语法

2/4、AMD异步模块定义/RequireJS

AMD - Asynchronous Module Definition,异步模块定义

  • 定义

    采用 异步 方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个 回调函数 中,等到所有依赖加载完成之后(依赖前置),这个回调函数才会运行(书写顺序和执行顺序不一定一致)。

  • 工具

    RequireJS - 一个工具库,主要用于客户端的 模块管理。它的模块管理遵守AMD规范;

    RequireJS 的基本思想是,通过 define 方法 - 将代码定义为模块,通过 require 方法 - 实现代码的模块加载。

  • 案例

     // 定义没有依赖的模块:
     define(function(){
         return 模块
     })
     // 定义有依赖的模块:
     define(['module1', 'module2'], function(m1, m2){
         return 模块
     })
    

例如:

image-20210713102656767

引入使用模块:

image-20210713102816660

3/4、CMD/SeaJS

CMD - 规范专门用于浏览器端,阿里借鉴整合了 Commonjs 的规范与 AMD 规范并进行了改进,模块的加载是异步的,模块使用时才会加载执行。(依赖就近

  • 模块输出

    只有一个出口:module.exports = value/exports.xxx=value

  • 加载模块:require(xxx)

SeaJs - CMD规范的实现,跟 RequireJs 类似,CMD是 SeaJs 推广过程中诞生的规范

  • 案例

      // 定义没有依赖的模块
      define(function(require, exports, module){
          exports.xxx = value
          module.exports = value
      })
      // 定义有依赖的模块
      define(function(require, exports, module){
          //引入依赖模块(同步)
          var module2 = require('./module2')
          //引入依赖模块(异步)
          require.async('./module3', function (m3) {
          })
          //暴露模块
          exports.xxx = value
      })
    

引入使用模块:

define(function (require) {
    var m1 = require('./module1')
    var m4 = require('./module4')
    m1.show()
    m4.show()
})

例如:

image-20210713114911923

4/4、ES6

2015年,ES6规范中,终于将 模块化 纳入 JavaScript 标准

  • 设计思想

    是尽量的静态化,使得 编译时 就能确定模块的依赖关系,以及输入和输出的变量

    关键字有 import,export,default,as,from

    export - 命令用于规定模块的对外接口

    import - 命令用于输入其他模块提供的功能

    export default 命令,为模块指定默认输出

image-20210713115042997