模块化开发
模块化开发是指在程序开发中,可以将每个功能脚本或文件单独提炼出来作为一个个独立的模块,模块之间相互关联,相互依赖,并对外暴露出一个入口文件。既方便开发者自己引用,也方便他人下载使用。比如前端经常用到的 Ant Design 组件,就可以看作是一个模块,在 Ant Design 中也按功能分了许多子模块,像轮播图组件、日期选择组件、导航组件等,模块化开发更便于使用和维护。
前端(浏览器)为什么要使用模块化开发
在传统的 HTML 应用中,脚本加载通过 script 标签的 src 属性,有几个脚本就引用几个资源。
<script src="main.js"></script>
<script src="index.js"></script>
<script src="detail.js"></script>
这样加载资源,有几个缺陷;
- 脚本的加载会阻塞 DOM 树的渲染
- 发起多次请求
- 脚本与脚本引用之间不方便
- 如果脚本之间相互依赖,就必须按照顺序加载,不利于维护
- 基于以上原因,不适合大型应用
script 标签提供了 defer 属性 和 async 属性来实现异步加载,不去阻塞 DOM 渲染。
- async 异步加载,加载的时候与 DOM 渲染并行,但是加载完成之后就开始执行,会中断 DOM 渲染,另外如果有多个 async 脚本,不能保证加载顺序。
- defer 延时加载,当 DOM 渲染完成以及其他脚本执行完成后,才会执行本脚本;如果有多个 defer 脚本,按序加载。但是 IE 浏览器不支持此属性,所以在应用的时候最好把两个属性都加上。
虽然 script 标签提供了方式实现异步加载,不阻塞 DOM 渲染,但是其他的问题还是没有解决,而模块化开发就能有效的解决以上问题,常见的模块加载器有 CommonJS、CMD、AMD 以及 ES6 模块加载,本文将探讨每个模块加载器的概念、特点、使用方式以及适用场景。
一、CommonJS
1. 概念
由于 JavaScript 没有模块体系和相应的包管理工具,所以不具备大型应用开发能力,因此 CommonJS 应运而生,而用 JavaScript 开发的服务端语言 NodeJS 就实现了 CommonJS 的规范。
2. 特点
- 一个文件就是一个模块,具备独立的作用域,不会污染全局。
- 依赖之间同步加载、执行,第一次加载的时候就全部执行了,并且缓存下来,后面执行直接使用缓存。
- __dirname代表当前模块文件所在的文件夹路径。
- __filename代表当前模块文件所在的文件夹路径+文件名。
3. 模块使用方式
- require 加载模块。
- module.exports 对外暴露接口。
4. 举例
module.exports = path; //对外暴露接口
const path = require('path');//加载模块
5. 应用场景
CommonJS 是代码同步加载,因为服务端的模块都是放在硬盘,加载的时间就是硬盘的读取时间。所以适用于服务端,并不适用于浏览器。
二、AMD(Asynchronous Module Definition)
1. 概念
上文说到 CommonJS 是同步加载,如果前面的程序加载时间太长,后面的程序要一直等待,在浏览器就会长时间出现空白现象,这对于现在越来越丰富的 Web 应用是无法忍受的,为了解决这个问题,AMD 规范就出现了,Require.js 是 AMD 规范的实现。
2. 特点
- 异步并行加载,不阻塞 DOM 渲染。
- 提前执行(预执行),在模块使用之前就已经执行完毕。
3. 模块使用方式
- define(callback),该函数接收一个回调函数,用来定义模块。
- require([module], callback),该函数接收两个参数,一个是要加载的模块名称数组,一个是回调函数,用来加载模块。
- requirejs.config(obj),用来对 Require.js 进行配置。
4. 举例
需要引入 require.js,data-main 属性为统一的入口资源文件路径,在 require.js 加载之后加载。
<script src="js/require.js" data-main="js/main"></script>
// 定义模块index.js
define(['index'], function(){
function person(){
console.log('jack');
}
return {
person: person
};
});
// 加载模块
require(['index'], function (module){
module.person();
});
5. 应用场景
可以在浏览器使用,也可以在 Rhino / Node 等环境使用。
三、CMD(Common Module Definition)
1. 概念
CMD 是通用模块加载,Sea.js 是 CMD 的规范实现,CMD 规范要解决的问题与 AMD 一样,只不过是对依赖模块的执行时机不同 ,CMD 是模块需要用到的时候再去执行,推崇就近依赖。
2. 特点
- 异步并行加载,也不会阻塞 DOM 渲染。
- 按需执行(需要的时候才执行)。
3. 模块使用方式
- require,加载模块。
- seajs.config(obj),用来对 Sea.js 进行配置。
- seajs.use(callback),用来在页面中加载一个或多个模块。
- define(callback),用来定义模块。
- require.async,用来在模块内部异步加载一个或多个模块。
- module.exports 与 exports 类似,都是对外提供接口 。
4. 举例
// 定义模块 a.js
define(function(require, exports, module) {
//引入模块
var $ = require('jquery.js')
//对外提供 price 属性
exports.price= 200;
});
// 加载模块
seajs.use(['a'],function(a){
console.log(a.price)//200
}
5. 应用场景
专注于 Web 浏览器
四、ES6 模块加载
1. 概念
ES6 也可以实现浏览器的模块化加载,需要在 script 标签中加入 “type=module” 属性,浏览器看到这个属性就知道这是个 ES6模块,会等到 DOM 渲染完成,再执行该模块,如果有多个 “type=module” ,浏览器会按序加载,相当于添加了 defer 属性。
2. 特点
在 ES6 中模块化加载需要注意的几点:
- 默认严格模式(严格模式的顶层 this 指向 undefined,而不是 windows);
- js后缀不可省略;
- 变量只在本模块作用域内有效,也就是局部变量;
- 同一个模块加载多次,只执行一次。
3. 模块化使用方式
- import ,加载模块
- export default (指定模块默认输出)向外暴露接口,如果没有 default,引用的时候需要带上{}
//1.使用export default
//main.js
import index from './index.js';
index();
//index.js
export default function index(){
console.log(1)
}
//2.使用export
//main.js
import {index} from './index.js';
index();
//index.js
export function index(){
console.log(1)
}
4. 举例
//在网页中引入模块
<script type="module">
import main from './requireTest.js';
main();
</script>
//脚本与脚本之间的引用与导出
//requireTest.js
import index from './requireIndex.js';
export default function main() {
index();
}
//requireIndex.js
export default function index(){
console.log(3333);
}
5. 应用场景
适用于浏览器
五、各模块加载器区别
1. 模块使用方式
CommonJS :require 引用模块 module.exports 暴露接口
AMD:require 引用模块 使用 define 函数的 return 暴露接口
CMD:require 引用模块 module.exports 或 exports 暴露接口
ES6:import 引用模块 export default 暴露接口
2. 模块加载方式
CommonJS :运行时加载,一个模块就是一个对象,加载一个模块就是加载这个模块的所有方法,然后读取其中需要的方法。
AMD:并行加载,提前执行。
CMD:并行加载,按需执行。
ES6 :编译时加载,在模块编译时就完成加载,引用的时候只加载需要的方法,其他方法不加载。
3. 实现模块规范
CommonJS :NodeJS
AMD:RequireJS
CMD:SeaJS
ES6 :原生JS
4. 输出的是引用还是拷贝
CommonJS :输出的是值的拷贝,不会受其内部影响。
ES6 :输出的是值的引用,会受其内部影响。
5. 模块内顶层 this 对象
CommonJS :指向的是当前模块对象。
ES6 :指向的是 undefined。