前端模块化:CommonJS,AMD,CMD,ES6发展

前端模块化

模块化开发可以提高代码复用率,方便代码管理。一般来说一个文件就是一个模块,得外暴露变量或者函数。目前比较流行的模块化的规范有:AMD、CMD和CommonJs以及ES6 的 module。

AMD(Asynchronous Module Definition)CMD(Common Module Definition)CommonJsES6 Module
require.js 在推广过程中对模块化定义的规范产出的。 sea.js在推广过程中对模块化定义的规范产出的。 Node推广使用。  
提前执行(异步加载:依赖先执行)+延迟执行。 延迟执行(运行到需要时加载,根据顺序执行)。    
浏览器 浏览器 服务器端 浏览器和服务器
依赖前置,提前执行(在定义模块的时候就声明依赖的模块) 依赖就近,延迟执行(只有在需要用的时候才去require 执行)    
CommonJsES6 Module
运行时加载;CommonJs模块就是对象(module.exports属性)),即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法。 编译时加载;ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块。
输出的是值的拷贝(一旦输出一个值,模块内部的变化就影响不到这个值。) 输出的是值的引用(JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。即原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。)

CommonJS

commonjs主要由node推广使用。

关键命令:module.exports = 、require()

例子:

// 定义模块math.js

function add(a, b) {
  return a + b;
}
module.exports = { //对外暴露的函数、变量
  add: add
}
// main.js 

// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math');
math.add(2, 5);

// sever.js

// 引用核心模块时,不需要带路径
var http = require("http"),
    PORT = 8000;

http.createServer(function(req, res){
    res.end("Hello World");
}).listen(PORT);

console.log("listenning to " + PORT);

下面的例子可以体现[同步/阻塞式加载]的特性

// timeout.js
var EXE_TIME = 2;

(function(second){
    var start = +new Date();
    while(start + second*1000 > new Date()){}
})(EXE_TIME)

console.log("2000ms executed")
// main.js
require('./timeout');   // sync load
console.log('done!');

如果commonjs require方法是异步的话,下面这么写就会报错

//CommonJS Syntax
var Employee = require("types/Employee");

function Programmer (){
    //do something
}

Programmer.prototype = new Employee();

//如果 require call 是异步的,那么肯定 error
//因为在执行这句前 Employee 模块根本来不及加载进来

从下图可以看出,同步加载对服务器/本地环境并不是问题。-> 浏览器环境会有问题!!

 

CommonJS是同步加载的,因此更适合服务器端。只有加载完成之后才能进行下面的操作。因为在服务器端模块文件一般存放在本地,再加上有缓存,加载速度十分快。
因此这种就不适合用在浏览器端,浏览器端的各个 script 标签中的文件来自各个服务器,如果上个模块加载的时间很长,就会导致浏览器“假死”,因此浏览器端我们采用另外一种异步的加载方式AMD。

AMD(require.js)

关键命令:define()require()

AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,才会运行这个回调函数。这里介绍用require.js实现AMD规范的模块化:用require.config()指定引用路径等,用define()定义模块,用require()加载模块。

//AMD Wrapper
define(
    ["types/Employee"],  //依赖
    function(Employee){  //这个回调会在所有依赖都被加载后才执行
        function Programmer(){
            //do something
        };

        Programmer.prototype = new Employee();
        return Programmer;  //return Constructor
    }
)

Require.js使用例子:

/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>


/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
});

// 定义方式一:定义math.js模块
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});

// 定义方式二:定义一个依赖underscore.js的模块
define(['underscore'],function(_){
  var classify = function(list){
    _.countBy(list,function(num){
      return num > 30 ? 'old' : 'young';
    })
  };
  return {
    classify :classify
  };
})

// 引用模块,将模块放在[]内
require(['jquery', 'math'],function($, math){
  var sum = math.add(10,20);
  $("#sum").html(sum);
});

引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;
如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。

requirejs最佳实践

require([
    'React',    // 尽量使用 ModuleID
    'IScroll',
    'FastClick'
    'navBar',   // 和同目录下的 js 文件
    'tabBar',
], function(
    React,      // Export
    IScroll
    FastClick
    NavBar,
    TabBar,
){});

config

require.config({
    // 查找根路径,当加载包含协议或以/开头、.js结尾的文件时不启用
    baseUrl: "./js",
    // 配置 ModuleID 与 路径 的映射
    paths: {
        React: "lib/react-with-addons",
        FastClick: "http://cdn.bootcss.com/fastclick/1.0.3/fastclick.min",
        IScroll: "lib/iscroll",
    },
    // 为那些“全局变量注入”型脚本做依赖和导出配置
    shim: {
        'IScroll': {
            exports: "IScroll"
        },
    },
    // 从 CommonJS 包中加载模块
    packages: [
        {
            name: "ReactChart",
            location: "lib/react-chart",
            main: "index"
        }
    ]
})

优化打包

node r.js -o build.js

// build.js
// 简单的说,要把所有配置 repeat 一遍
({
    appDir: './src',
    baseUrl: './js',
    dir: './dist',
    modules: [
        {
            name: 'app'
        }
    ],
    fileExclusionRegExp: /^(r|build)\.js$/,
    optimizeCss: 'standard',
    removeCombined: true,
    paths: {
        React : "lib/react-with-addons",
        FastClick: "http://cdn.bootcss.com/fastclick/1.0.3/fastclick.min",
        IScroll: "lib/iscroll"
    },
    shim: {
        'IScroll': {
            exports: "IScroll"
        },
    },
    packages: [
        {
            name: "ReactChart",
            location: "lib/react-chart",
            main: "index"
        }
    ]
})

CMD(sea.js)

CMD与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。

/** AMD写法 **/
define(["a", "b", "c"], function(a, b, c) { 
     // 等于在最前面声明并初始化了要用到的所有模块
    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的小例子🌰:

/** 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

关键命令:exportexport detaultimport

ES6 在语言标准的层面上,实现了模块功能,浏览器和服务器通用的模块解决方案。

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };

/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。

 

posted @ 2020-04-22 17:10  剑仙6  阅读(349)  评论(0编辑  收藏  举报
欢迎访问个人网站www.qingchun.在线