目录
五. CommonJS vs AMD vs CMD vs ES6
一. 什么是模块化
- 将一个复杂的程序,依据一定的规则(规范)封装成一个或多个块(文件)
- 块的内部实现是私有的,只暴露一些接口(方法)供外部使用
二. 模块化的发展过程
2.1 无模块化时代
- 在 Ajax 还未提出之前,JavaScript 只用来在网页上进行表单校验提交,渲染 DOM
============ 某 .js 文件 ============ ============ 某 .js 文件 ============ ============ 某 .js 文件 ============ var str, num; function submit() { str = document.getElementById("xx").value; if (str) { ... } else { ... } ... num = 1; for (var i = 0; i < 10; i++) { num++; ... } } form.submit(); ============ 某 .html 文件 ============ ============ 某 .html 文件 ============ ============ 某 .html 文件 ============ <script type="text/javascript" src="a.js"></script> <script type="text/javascript" src="b.js"></script> <script type="text/javascript" src="main.js"></script>
- 缺点:全局变量污染、函数命名冲突、文件依赖顺序
2.2 模块化雏形时代
- 2006 年,Ajax 的概念被提出,前端拥有了 主动向服务端发送请求,并操作返回数据 的能力,传统网页向 “富客户端” 发展
- 出现了简单的 “功能对象” 封装
2.2.1 namespace 模式
- 优点:减少了全局变量
- 缺点:数据不安全(外部可以直接修改模块内部的数据),模块名称会暴露在全局,存在命名冲突,依赖顺序问题
============ 模块 .js 文件 ============ ============ 模块 .js 文件 ============ ============ 模块 .js 文件 ============ var myModule = { first_name: "www.", second_name: "baidu.com", getFullName: function () { return this.first_name + this.second_name; }, }; ============ .html文件 调用 模块.js ============ ============ .html文件 调用 模块.js ============ ============ .html文件 调用 模块.js ============ myModule.first_name = "img."; console.log(myModule.getFullName());
2.2.2 自执行匿名函数(闭包)模式
- 优点:变量、方法全局隐藏,模块私有化
- 缺点:模块名称会暴露在全局,存在命名冲突,依赖顺序问题
============ 模块 .js 文件 ============ ============ 模块 .js 文件 ============ ============ 模块 .js 文件 ============ (function (window) { let _moduleName = "module"; function setModuleName(name) { _moduleName = name; } function getModuleName() { return _moduleName; } window.moduleA = { setModuleName, getModuleName }; })(window); ============ .html文件 调用 模块.js ============ ============ .html文件 调用 模块.js ============ ============ .html文件 调用 模块.js ============ moduleA.setModuleName("html-module"); console.log(moduleA.getModuleName()); console.log(moduleA._moduleName); //模块不暴露,无法访问模块内属性方法
2.2.3 模块化雏形时代问题总结
- 如何安全的封装一个模块的代码?(不污染模块外的任何代码)
- 如何唯一标识一个模块?
- 如何优雅的把模块的 API 暴漏出去?(不能增加全局变量)
- 如何方便的使用所依赖的模块?
三. 模块化规范
3.1 CommonJS
- 2009 年 Node.js 发布,采用 CommonJS 模块规范
CommonJS 模块规范特点:
- 每个文件都是一个模块实例,代码运行在模块作用域,不会污染全局作用域
- 文件内通过 require 对象引入指定模块,通过 exports 对象来向外暴漏 API,文件内定义的变量、函数,都是私有的,对其他文件不可见
- 每个模块加载一次之后就会被 缓存
- 所有文件加载均是 同步完成,加载的顺序,按照其在代码中出现的顺序
- 模块输出的是一个值的拷贝,模块内部的变化不会影响该值
============ 模块 .js 文件 ============ ============ 模块 .js 文件 ============ ============ 模块 .js 文件 ============ let _moduleName = "module"; function setModuleName(name) { _moduleName = name; } function getModuleName() { return _moduleName; } module.exports = { setModuleName, getModuleName }; ============ .html文件 调用 模块.js ============ ============ .html文件 调用 模块.js ============ ============ .html文件 调用 模块.js ============ import { getModuleName, setModuleName } from "./es6.module"; setModuleName("es6 Module"); console.log(getModuleName());
- 缺点:模块同步加载导致:资源消耗多,等待时间长,适用于服务器编程
3.2 AMD / RequireJS
- Commonjs 局限性:
- 基于 Node.js 原生 API 在服务端可以实现模块 同步加载,但仅局限于服务端,客户端如果同步加载依赖的话,时间消耗非常大,于是 AMD 规范诞生了
何谓 AMD?- AMD 是 ”Asynchronous Module Definition” 的缩写,意思就是 ”异步模块定义”
- 它采用 异步方式 加载模块,模块的加载不影响它后面语句的运行
- 所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后(依赖前置),这个回调函数才会运行
何谓 RequireJS?- RequireJS 是一个工具库,主要用于客户端的模块管理,它的模块管理遵守 AMD 规范
- RequireJS 的基本思想:通过 define 方法将代码定义为模块,通过 require 方法实现代码的模块加载
举个栗子:定义两个模块,一个没有依赖,一个有依赖// module1.js 定义没有依赖的模块 define(function () { let _moduleName = "module"; function getName() { return _moduleName; } return { getName }; // 暴露模块 }); // module2.js 定义有依赖的模块 define(["module1"], function (module1) { let _firstName = "AMD"; function getFullName() { return _firstName + " " + module1.getName(); } function setFirstName(name) { _firstName = name; } // 暴露模块 return { _firstName, getFullName, setFirstName }; });
- 在 main.js 中使用这两个模块
- main.js 相当于一个工具库,对各种模块路径、基本使用方法进行封装
// mian.js require.config({ paths: { module1: "./modules/module1", module2: "./modules/module2", // 第三方库模块 jquery: "./libs/jquery.min", }, }); require(["module2", "jquery"], function (module2, jquery) { console.log(module2.getFullName()); module2.setFirstName("AMD-AMD"); console.log(module2.getFullName()); console.log(module2._firstName); jquery("#moduleId").html("<i>My name is jquery-module</i>"); });
- .html 中引入工具库,并定义 js 主文件
// .html 中引入工具库,并定义js主文件 <script data-main="./main" src="./libs/require.js"></script>
- AMD / RequireJS 特点:浏览器直接运行无需编译,异步加载,依赖关系清晰
3.3 CMD / SeaJS
- 借鉴了 Commonjs 的规范与 AMD 规范,国内(阿里)诞生了一个 CMD(Common Module Definition)规范,CMD 规范专门用于浏览器端
- 跟 RequireJs 类似,SeaJs 是 CMD 规范的实现
- CMD 是 SeaJs 推广过程中诞生的规范,CMD 借鉴了很多 AMD 和 Commonjs 优点
与 AMD 非常类似,CMD 规范(2011)具有以下特点:
- define 定义模块,require 加载模块,exports 暴露变量
- 不同于 AMD 的依赖前置,CMD 推崇依赖就近(需要的时候再加载)
- 推崇 api 功能单一,一个模块干一件事
// module.1 define(function (require, exports, module) { module.exports = { msg: "I am module1", }; }) // module.2 define(function (require, exports, module) { var module2 = require("./module1"); function show() { console.log("同步引入依赖模块1 " + module2.msg); } exports.showModule = show; }); // main.js define(function (require) { var m2 = require("./modules/module2"); m2.showModule(); }); // .html中引入工具库,并定义 js 主文件 <script type="text/javascript" src="./libs/sea.js"></script> <script type="text/javascript"> seajs.use('./main') </script>
3.4 AMD vs CMD
- AMD 推崇依赖前置
- CMD 推崇依赖就近
// AMD define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() ... b.doSomething() ... }) // CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() ... var b = require('./b') // 依赖可以就近书写 b.doSomething() ... }
四. ES6 与 JavaScript模块化 关系
4.1 ES6 中的模块化示例
- 2015 年,ES6 规范中,将 JavaScript模块化 纳入 JavaScript 标准
- ES6 中的模块化,在 CommonJS 的基础上进行改造,关键字有 import,export,default,as,from
// 模块js let _moduleName = "module"; function setModuleName(name) { _moduleName = name; } function getModuleName() { return _moduleName; } export { setModuleName, getModuleName }; // 调用js import { getModuleName, setModuleName } from "./es6.module"; setModuleName("es6 Module"); console.log(getModuleName());
4.2 CommonJS vs ES6
- 模块内部值的改变 是否影响 外部引用的该模块?
- CommonJS 模块输出的是一个值的拷贝,原来模块中的值改变,不会影响已经加载模块中的值
- ES6 模块输出的是值的只读引用,模块内值改变,引用也改变
模块导出内容范围?- CommonJS 模块是运行时加载,加载的是整个模块,所有的接口会全部加载
- ES6 模块是编译时输出接口,可以单独加载其中的某个接口
五. CommonJS vs AMD vs CMD vs ES6
- CommonJS 规范主要用于服务端编程,加载模块是同步的;不适合在浏览器环境,因为浏览器资源是异步加载的,存在阻塞加载,也因此有了 AMD、CMD
- AMD 规范在浏览器环境中,异步加载模块,而且可以并行加载多个模块
- CMD 规范与 AMD 规范很相似,都用于浏览器编程,依赖就近,代码更简单
- ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为 浏览器 和 服务器 通用的模块解决方案
JavaScript 模块化详解
最新推荐文章于 2024-09-08 01:04:23 发布