# 模块化概述

模块化,即将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。可以更方便地使用别人的代码,想要什么功能,就加载什么模块。 例如CSS的@import

JavaScript原本的模块化可以通过函数或对象实现:

函数: 将几个函数封装到一个文件,需要的时候记载这个文件,调用其中函数即可。 但是这样会污染全局变量,造成命名冲突。

对象: 模块写成一个对象,模块成员都封装在对象里,通过调用对象属性,访问使用模块成员。但同时也暴露了模块成员,外部可以修改模块内部状态。

而JavaScript历史上并没有模块体系,无法将一个大程序拆分,对开发大型的、复杂的项目形成了巨大障碍。所以,社区制定了一些模块加载方案,最主要的有 CommonJSAMDCMD

模块化开发的前提就是所有开发者必须以同样的方式编写模块,否则你有你的写法,我有我的写法,就会乱套,所以出现了四种规范 CommonJS,AMD,CMD,ES6模块化。

  • CommonJS: 服务器端模块化,如Node.js
  • AMD: 浏览器模块化,RequireJS 在推广过程中对模块定义的规范化的产出
  • CMD: 浏览器模块化,国内发展出来的,CMD有个浏览器的实现SeaJSSeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同
  • ES6 Module: 浏览器模块化,ES6标准的模块化,用以取代 CommonJS 和 AMD 规范。

# AMD

AMD规范不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS

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

# 1. 引入require.js文件

<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>

# 2. 模块编写 (define)

require.js中,每个模块就是一个js文件。RequireJS定义了一个函数 define,它是全局变量,用来定义模块:

define(id?, dependencies?, factory);

参数说明:

参数 说明
id string (可选)模块名称
dependencies array (可选)依赖数组,表示该模块所依赖的模块,模块名不需要加js后缀
factory array/object 模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值

# 导出模块

require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。

假定现在顶一个模块math.js,如果不需要依赖其他模块,则:

define(function(){
    var num = 10;
    function add(a, b){ return a + b };

    // 声明了两个属性进行导出
    return {
        num,
        add
    }
})

# 导入模块

例子:main.js文件加载两个模块(jquery, math)并在回调函数中使用:

// main.js 文件
require(['jquery', 'math'], function ($, math){
    // 回调函数中的math即为刚才导出的{ num, add }
  alert( math.add(1,2) )   // 3
});

# 模块加载的配置

上面案例中,引入三个模块(jquery, math),默认情况下,require.js假定这两个模块与main.js在同一个目录,文件名分别为jquery.jsmath.js,然后自动加载。

使用require.config()方法,可以对模块的加载行为进行自定义:

require.config({
    baseUrl: "./js",
  paths: {
    "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min",
    "math": "lib/math.min",    // 省略js后缀
  }
});
配置项 说明
baseUrl string 定义读取模块的根目录
paths object 定义模块路径与别名,如上例模块路径lib/math.min,用math指代

# 兼容性

requireJS兼容性

# CMD

CMD规范是国内发展出来的,CMD的浏览器的实现是sea.js,SeaJS与requireJS类似,只不过在模块定义方式和模块加载(运行、解析)时机上有所不同。

导入:

<script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>

# CMD 模块编写

在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:

define(function(require, exports, module) {

  // 模块代码

});
参数 说明
require 可以用来导入其他模块
exports 可以把该模块内容属性方法导出
module 一个对象,存储了与当前模块相关联的一些属性和方法

CMD 模块定义规范 (opens new window)

# 定义模块 (define)

// math.js文件
define(function(require, exports, module) {
    require('./jquery.min.js')     // require 导入jquery
    $.ajax()
    exports.add = function(a, b){ return a+b };        // export导出函数add
});

# 导入模块 (seajs.use)

seajs.use(['./math.js'], function (math) {
   math.add(1, 2);    // 3
});

# 配置Sea.js ( seajs.config() )

配置项 说明
base string 定义读取模块的根目录
alias object 定义模块路径与别名,如模块路径../lib/jquery.min.js,用jquery指代
seajs.config({
    base:"./js"  
    alias:{
        'jquery':'../lib/jquery.min.js'
    }
});

# AMD 与 CMD 的区别

  • AMD是依赖关系前置,在定义模块的时候就要声明其依赖的模块;
  • CMD是按需加载依赖就近,只有在用到某个模块的时候再去require:
// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此处略去 100 行
  var b = require('./b') // 依赖可以就近书写
  b.doSomething()
  // ...
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  a.doSomething()
  // 此处略去 100 行
  b.doSomething()
  ...
})

# 兼容性

SeaJS兼容性

# ES6中的模块化

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

注意:ES6模块中,顶层的this指向 undefined,即不应该在顶层代码使用this

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

# 1. export命令

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。export可以让我们把变量,函数,对象进行模块化,提供外部调用接口,让外部进行引用。

export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

或在文件末尾多变量输出:

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

export命令除了输出变量,还可以输出函数或类(class)。

exprot function fun(){ }

function fun() {}

exprot {fun};

有些时候并不想暴露模块里边的变量名称,还可使用as关键字对变量进行重命名

export {
    v1 as firstName,
    v2 as lastName,
    v3 as lastName
};

# 2. export default 命令

  1. exportexport default均可用于导出常量、函数、文件、模块等
  2. 你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
  3. 在一个文件或模块中,exportimport可以有多个,export default仅有一个
  4. 通过export方式导出,在导入时要加{ }export default则不需要
var name="李四";
export { name }
//import { name } from "./a.js"
可以写成:
var name="李四";
export default name
//import name from "./a.js" 这里name不需要大括号

# 3. import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

export对应的导入方式

export var a ='js';
export function add(a,b){
    return a+b;
}

import {a,add} from './temp';

//也可以分开写
import a from './temp';
import add from './temp';

export defalut对应的导入方式

var a ='js';
function add(a,b){
    return a+b;
}
export defalut {a, add}

import obj from './temp';
  • import用as方式引入 (多个变量用一个空对象来代理,你所有的方法和属性都是在types命名空间)
const LOGIN = 'login';
const LOGOUT = 'logout';
const TITLE = 'title'
export {LOGIN,LOGOUT,TITLE}
import * as types from './temp.js' //你所有的方法和属性都是在types命名空间
// 调用里面里面的值可以 这样做
types.LOGIN
types.LOGOUT
types.TITLE

由于ES6的模块化在浏览器端兼容性较差,不能直接在浏览器中预览,必须要使用Babel进行编译之后正常看到结果。