前端系列-2 模块化CommonJs+AMD+CMD+ES6

模块化

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对象

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

module变量是一个对象,表示当前模块;有一个exports属性,表示模块对外输出的值。

module对象除了这个exports属性外,还有以下属性:

1 id 模块的标识符,带有绝对路径的模块文件名;

2 filename: 模块的文件名,带有绝对路径;

3 parent: 返回一个对象,调用该模块的对象;

4 loaded:boolean,表示该模块是否加载完成;

5 children:数组,该模块内用到的其他模块;

1.2 全局函数require

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

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

require是加载模块,并给导入的模块定义名称。

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

1.3 案例

js 复制代码
// 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函数。

js 复制代码
// 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 复制代码
<!--在html中-->
<script data-main="./index.js" src="./js/require.js"></script>

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

2.2 定义模块

AMD使用define定义模块的信息、模块的依赖、函数。

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

js 复制代码
// 定义没有依赖的模块
define(functioin(){
  return 模块对象
})


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

2.3 引入模块

js 复制代码
// 主模块中用过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 案例

案例使用的文件路径为:

shell 复制代码
-lib
 -require.js #依赖库
-js
 -module1.js
 -module2.js
-index.html
-index.js

index.js文件

javascript 复制代码
(function (){
    requirejs.config({
        paths: {
            baseUrl: './js/',
            module1: './module1',
            module2: './module2'
        }
    })

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

module1.js文件

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

module2.js文件

javascript 复制代码
define(function () {
    function print2 (param) { 
        console.log(" -m2- "+param)
    }
    return { print2 }
})

index.html文件

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浏览器会因为跨域报错,可以进行如下参数设置:

shell 复制代码
--allow-file-access-from-files  --user-data-dir="空文件夹" --disable-web-security

3.CMD

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

3.1 引入主模块

html 复制代码
<script src="./lib/sea.js"></script>
<script> seajs.use('./index.js')</script>

3.2 定义和加载模块

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

js 复制代码
define(function(require, exports, module) {
 //...
})

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

require用法同AMD:

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

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

js 复制代码
seajs.use(['module1', 'module2'], function(m1, m2){
    //...
});

3.3 案例

案例使用的文件路径为:

shell 复制代码
-lib
 -sea.js #依赖库
-js
 -module1.js
 -module2.js
-index.html
-index.js

index.js文件

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

module1.js文件

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

module2.js文件

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

index.html文件

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 { }形式。

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

等价于:

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

export {
	test1,
	fun1
}

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

javascript 复制代码
// ...
export {
	test1,
	fun1 as fun1,
	fun1 as fun2
}

import:

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

javascript 复制代码
// index.js
import {test1, fun1} from "./module1";

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

或者:

javascript 复制代码
// index.js
import * as m1 from "./module1";

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

导入时也可以指定别名:

javascript 复制代码
import {test1, fun1 ad fun} from "./module1";

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

4.2 export default和import

一个模块可以使用export default导出一个对象(变量、函数、类),import导入deault时不需要使用大括号。

在module1.js中使用export default导出一个默认输出:

javascript 复制代码
// module1.js
export default function test1(){
	console.log("hello world")
}

在index.js中使用import导入:

javascript 复制代码
// index.js
import test from "module1";
test();

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

相关推荐
Cao_Ron3 分钟前
用 SVG image symbol 画 ECharts graph 圆形节点
前端
hxy06017 分钟前
React-Call让你的React组件像异步函数一样调用
前端·react.js
Hilaku8 分钟前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员
京东云开发者10 分钟前
GPT-5.6、ChatGPT Work 与 Codex 更新食用指南
前端
Cao_Ron21 分钟前
用 ECharts graph 画出卡片式矩形节点
前端
skiyee30 分钟前
换个底座的 Wot Starter 居然让 AI 更懂项目!
前端·ai编程
anyup36 分钟前
这一套绝了!AI 大模型写故事,星云 SDK 驱动 3D 数字人实时演绎
前端·人工智能·aigc
耀耀切克闹灬1 小时前
短链跳转
前端
孟陬1 小时前
高质量全方位的 AI 工具 sse-stuntman — Mock SSE 接口
前端·node.js·openai
Cao_Ron1 小时前
给 ECharts 6 graph 节点叠加角标:从原理到性能优化
前端