admin管理员组

文章数量:1530026

模块化

js中, 一个文件就是一个模块,模块化可以提高代码利用率而减少重复代码,将相同的逻辑维护在一个模块中,分而治之,提高代码的可维护性;另外,模块化的出现可以在工程中引入第三方模块,极大提高开发效率。
在模块化出现前,只能通过<script>标签在html中导入,并根据依赖控制导入顺序。CommonJS出现后,模块化技术不断发展,目前主流的js模块化规范有CommonJS, AMD/CMD以及ES6等模块系统,按照模块化的发展可排序为: CommonJS -> AMD/CMD -> ES6.
CommonJS用于服务端,AMD/CMD用于浏览器端,ES6是服务端和浏览器端通用的模块解决方案。

1.CommonJS规范

CommonJS规范是NodeJs默认的模块化的规范,适用于服务器端,不适用于浏览器端(理由在后面介绍).

在CommonJS中, 每个js文件就是一个模块,有自己的作用域:文件内定义的变量、函数、类都是私有的,其他模块不可见;可以通过module.exports和require组合的方式实现跨模块访问。

1.1 module对象

module.exports = {
    // 可以导出内部的变量、方法等
}

module变量是一个对象,表示当前模块;有一个exports属性,表示模块对外输出的值。
module对象除了这个exports属性外,还有以下属性:
[1] id 模块的标识符,带有绝对路径的模块文件名;
[2] filename: 模块的文件名,带有绝对路径;
[3] parent: 返回一个对象,调用该模块的对象;
[4] loaded:boolean,表示该模块是否加载完成;
[5] children:数组,该模块内用到的其他模块;

1.2 全局函数require

一般而言,导出是为了导入,不然导出就没有了意义。CommonJS规范通过require方法进行模块的导入.

// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math');
 
// 引用核心模块时,不需要带路径
var http = require('http');

require是加载模块,并给导入的模块定义名称。
require基于缓存加载模块,所有加载的模块都会缓存在require.cache中,之后再次加载会直接从缓存中读取(每个模块只会被加载一次)。require加载返回的对象为module.exports属性对象。如果被加载的模块没有给exports属性对象赋值,则加载后返回的对象为空对象{}。

1.3 案例

// entry.js
var myModule = require("./module.js")
console.log(myModule)

---

// module.js
var num = 1
function addNum(val){
    return num + val
}
module.exports = {
 num,
 addNum
}

运行结果如下:

{num: 1, addNum: ƒ}

表示myModule对象包含一个属性值num=1,和一个addNum函数。

// entry.js
var myModule = require("./module.js")
console.log(myModule)

---

// module.js
var num = 1
function addNum(val){
    return num + val
}

运行结果如下:

{}

表示myModule对象为空对象。

1.4 特点

CommonJs同步加载模块,即后面的模块需要等待前面的模块加载完成。对于服务器端,模块文件存在与本地文件系统,不存在问题。浏览器需要从服务器下载模块并加载,由于渲染是顺序进行的,当script标签阻塞时浏览器不会继续向下渲染DOM,因此页面会失去响应。因此,浏览器端只能采用异步方式加载模块。

2.AMD

AMD模块规范中,模块的加载是异步的,专用于浏览器端;使用时需要依赖第三方库RequireJs.

2.1 引入主模块

在html中通过<script>标签引入require.js库文件时,通过data-main属性指定主模块的路径:

<!--在html中-->
<script data-main="./index.js" src="./js/require.js"></script>

当require.js加载并执行后,会根据data-main属性指定的URL加载并运行主模块(此时为index.js).

2.2 定义模块

AMD使用define定义模块的信息、模块的依赖、函数。
第一个参数为模块的名称(不常使用,一般用后面两个参数),第二个参数为数组类型,依赖的模块,第三个参数为函数(用于返回定义的模块信息)。

// 定义没有依赖的模块
define(functioin(){
  return 模块对象
})


// 定义有依赖的模块
define(['module1','module2'], function(m1, m2){
   // m1对应导入的module1对象, m2对应导入的module2对象
  return 模块对象
})

2.3 引入模块

// 主模块中用过requirejs.config指定模块的路径
requirejs.config({
    baseUrl: 'js/',
    paths: {
        module1: './modules/module1',
        module2: './modules/module2'
    }
})

requirejs(['module1'], function (m1) {
    // 操作m1模块对象
})

通过requirejs函数引入模块,第一个参数为依赖的模块组数,第二个参数为回调函数。
另外,在主模块中通过requirejs.config指定模块的路径,baseUrl指定一个路径,后续配置路径时以此为相对路径(可选属性;paths中配置各个模块与模块文件路径的映射关系。

2.4 案例

案例使用的文件路径为:

-lib
 -require.js #依赖库
-js
 -module1.js
 -module2.js
-index.html
-index.js

index.js文件

(function (){
    requirejs.config({
        paths: {
            baseUrl: './js/',
            module1: './module1',
            module2: './module2'
        }
    })

    requirejs(['module1'], function (m1) {
        m1.print1("hello world")
    })
    
})()

module1.js文件

define(['module2'], function (m2) {
    function print1 (param) {
        m2.print2(" -m1- "+param);
    }
    return { print1 }
})

module2.js文件

define(function () {
    function print2 (param) { 
        console.log(" -m2- "+param)
    }
    return { print2 }
})

index.html文件

<!DOCTYPE html>  
<html>  
<head>  
    <script src="./lib/require.js" data-main="./index.js"></script>  
</head>  
<body>  
</body>  
</html>

浏览器console运行结果如下:

-m2-  -m1- hello world

注意:本地测试——直接通过浏览器访问html文件时,Chrome浏览器会因为跨域报错,可以进行如下参数设置:

--allow-file-access-from-files  --user-data-dir="空文件夹" --disable-web-security

3.CMD

CMD与AMD类似使用异步加载模块的机制,作为浏览器端的模块规范,区别在于CMD依赖提前下载,而CMD延迟下载,CMD的实现依赖于SeaJS。

3.1 引入主模块

<script src="./lib/sea.js"></script>
<script> seajs.use('./index.js')</script>

3.2 定义和加载模块

使用define定义模块,如下所示:

define(function(require, exports, module) {
 //...
})

其中:[1] require函数用于加载其他模块; [2] exports对象用于向外提供接口,导出对象或者方法;[3]存储当前模块的属性和方法。
require用法同AMD:

define(function(require, exports, module) {
 const m1 = require("./module1");
 m1.print1("hello world");
})

SeaJs也有use功能(在3.1章节中使用到),在需要先引入锁依赖的模块:

seajs.use(['module1', 'module2'], function(m1, m2){
    //...
});

3.3 案例

案例使用的文件路径为:

-lib
 -sea.js #依赖库
-js
 -module1.js
 -module2.js
-index.html
-index.js

index.js文件

define(function(require, exports, module) {
 const m1 = require("./js/module1");
    m1.print1("hello world");
})

module1.js文件

define(function(require, exports, module) {
 const m2 = require("./module2");
    exports.print1 = function(param) {
        m2.print2(" -m1- "+param)
    }
})

module2.js文件

define(function(require, exports, module) {
    exports.print2 = function(param) {
        console.log(" -m2- "+param)
    }
})

index.html文件

<!DOCTYPE html>  
<html>  
<head>  
    <script src="./lib/sea.js"></script>
 <script> seajs.use('./index.js')</script>
</head>  
<body>  
</body>  
</html>

浏览器console运行结果如下:

 -m2-  -m1- hello world

4.ES6

ES6使用export和import关键词进行模块的导入和导出。

4.1 export和import

可以使用export指定本模块需要导出的对象(变量、方法、类等信息);在其他模块中通过import导入。
export:
export有两种使用方式,推荐 export { }形式。

// module1.js
export var test1 = "1";
export var fun1 = function() {
	console.log("hello world")
}

等价于:

// module1.js
var test1 = "1";
var fun1 = function(){
	console.log("hello world");
}

export {
	test1,
	fun1
}

另外,导出时也可以执行别名, 如下所示:

// ...
export {
	test1,
	fun1 as fun1,
	fun1 as fun2
}

import:
使用import {...} from 模块 形式或使用import * as 别名 from 模块形式导入模块。

// index.js
import {test1, fun1} from "./module1";

console.log(test1); //1
fun1(); // hello world

或者:

// index.js
import * as m1 from "./module1";

console.log(m1.test1); //1
m1.fun1(); // hello world

导入时也可以指定别名:

import {test1, fun1 ad fun} from "./module1";

注意:使用 import{}和import * 不包括export default导出的对象。

4.2 export default和import

一个模块可以使用export default导出一个对象(变量、函数、类),import导入deault时不需要使用大括号。
在module1.js中使用export default导出一个默认输出:

// module1.js
export default function test1(){
	console.log("hello world")
}

在index.js中使用import导入:

// index.js
import test from "module1";
test();

导入默认导出项时,需要指定别名,此时为test.

本文标签: 系列CommonJSamdcmd