欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

前端模块化(CommonJs、AMD、CMD、ES6)

程序员文章站 2022-06-13 22:05:11
...

一、总述

前端模块化:CommonJS、AMD、CMD、ES6模块化

二、COMMONJS

  1. 定义:根据CommonJS规范,一个单独的文件就是一个模块,每一个模块就是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其模块读取,除非是定义为global对象的属性。
  2. 输出:模块只有一个输出,module.exports对象,一个模块输出的内容会被放在这个对象中。
  3. 加载:加载模块使用require.js方法,该方法读取一个文件并执行,返回文件内部的module.exports对象。

注:require是同步的,在服务器端实现很简单,但是在浏览器端,加载JS最佳方法是在document中插入script标签,但是脚本标签是异步的,故传统的commonJS模块在浏览器环境中无法正常加载。

三、AMD

  1. AMD(Asynchronous Module Definition),异步模块定义,实现在浏览器端模块化开发的规范。使用AMD规范进行页面开发需要用到对应的库函数,即RequireJs,实际上AMD是RequireJs在推广过程中对模块定义的规范化的产出。
  2. requireJs主要解决两个问题:
    (1)多个js文件kennel有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器;
    (2)js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长。
  3. 语法

(1)define:define(id?, dependences?, factory);

  • id:可选参数,用来定义模块的标识,若没有提供,则使用脚本文件名;
  • dependences:当前模块依赖的模块名称数组;
  • factory:工厂方法,模块初始化要执行的函数或对象。若是函数,应该只被执行一次;若是对象,此对象应该为模块的输出值。

(2)require:require([dependencies], function(){});

  • dependencies:依赖的模块;
  • function:回调函数,所有模块加载成功后被调用,加载的模块会以参数的形式传入。
  1. require()函数在加载依赖函数的时候是异步加载的,这样浏览器不会失去响应;在模块都加载成功后,指定的回调函数才会运行,解决了依赖性的问题。
  2. RequireJS 的基本思想为:通过一个函数来将所有所需要的或者说所依赖的模块实现装载进来,然后返回一个新的函数(模块)。

四、CMD

  1. CMD(Common Module Definition),通用模块定义,CMD浏览器实现是SeaJs。SeaJS与RequireJS类似,只不过在模块定义方法和模块加载时机上有所不同。
  2. 语法

(1)define:define(id?, dependence?, factory);

  • CMD推崇一个文件一个模块,故经常用文件名作为模块ID;
  • CMD推崇依赖就近,所以一般在factory中写依赖。

(2)define中的factory:function(require, exports, module);

  • require:方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口:require(id);
  • export:对象,对外提供模块接口;
  • module:对象,储存了与当前模块相关联的一些属性和方法。

五、AMD与CMD区别

  1. AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块;
  2. CMD推崇就近依赖,只有在用到某个模块的时候在去require;
  3. 两者最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不对。
  • 同样都是异步加载模块,AMD在加载模块完成过会执行该模块,所有模块都加载执行后会进入require的回调函数,执行主逻辑。这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,哪个先下载下来哪个先执行,但是主逻辑一定在所有依赖加载完成后才会执行;
  • CMD加载完某个依赖模块后并不执行,只是下载下来而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的;
/** requireJS和SeaJS可以互相实现**/
/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
     // 等于在最前面声明并初始化了要用到的所有模块
    a.doSomething();
    if (false) {
        // 即便没用到某个模块 b,但 b 还是提前执行了
        b.doSomething()
    } 
});

/** CMD写法 **/
define(function(require, exports, module) {
    var a = require('./a'); //在需要时申明
    a.doSomething();
    if (false) {
        var b = require('./b');
        b.doSomething();
    }
});

/** sea.js **/
// 定义模块 math.js
define(function(require, exports, module) {
    var $ = require('jquery.js');
    var add = function(a,b){
        return a+b;
    }
    exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
    var sum = math.add(1+2);
});

六、ES6 Module

  1. ES6模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入与输出的变量。CommonJS与AMD模块只能在运行时确定模块的依赖关系。
  2. 例如,CommonJ就是一个对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

以上代码实质上先整体加载fs模块生成一个对象_fs,然后再从_fs对象中读取用到的3个方法。这种加载叫做运行时加载,没有办法在编译时做到静态优化。
3. ES6模块不是对象,是通过export命令显式指定输出的代码,再通过import命令输入。

// ES6模块
import { stat, exists, readFile } from 'fs';

以上代码实质上是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即ES6模块在编译时就完成模块加载,效率要比commonJS高。

  1. ES6与CommonJS区别

(1)CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用;

  • CommonJs输出的是一个值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值;
  • JS引擎在遇到import的时候,会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变化会导致import加载的值变化。ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

(2)CommonJS模块是运行时加载,ES6是编译时输出接口

  • CommonJS时运行时加载,ES6是编译时加载。