js模块化规范
主流的模块化
目前流行的js模块化规范有CommonJS、AMD、CMD、ES6的模块系统。
CommonJS
CommonJS 的出发点:JS没有完善的模块系统,标准库较少,缺少包管理工具。NodeJS的兴起使得JS能在任何地方运行,特别是服务端,也达到了具备开发大型项目的能力,所以CommonJS应运而生。
Node.js是CommonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。module.exports定义当前模块对外输出的接口(不推荐使用exports),用require加载模块。
不推荐使用exports的原因是:
1.如果要导出一个键值对象{},则可以使用exports; 2.如果要导出一个函数或数组,只能对module.exports赋值;
所以,任何时候导出模块都用module.exports即可
CommonJS通过同步的方式加载模块。在服务器,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
CommonJS 规范
-
一个文件就是一个模块,拥有单独的作用域
如何实现单独的作用域?
JavaScript是一种函数式编程语言,支持闭包。把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。
//hello.js var s = 'Hello'; var name = 'world'; console.log(s + ' ' + name + '!');
Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行:
(function(){ var s = 'Hello'; var name = 'world'; console.log(s + ' ' + name + '!'); })();
-
普通方式定义的变量、函数、对象都属于该模块内
-
通过require来加载模块
require(xxx)
-
通过module.exports来暴露模块中的内容
module.exports=xxx
注意:
- 模块可以多次加载,但只会在第一次加载时候运行,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果
- 模块加载顺序,按照代码出现的顺序同步加载
- __dirname代表当前文件所在的文件夹路径
- __filename代表当前模块文件所在的文件夹路径+文件名
ES6模块化
ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export
和import
,另外还提供了export default
命令,为模块指定默认输出,对应的import语句不需要使用大括号,这也更趋近于AMD
的引用写法。
ES6的模块不是对象,import命令会被JavaScript引擎静态分析,在编译时就引入模块代码
,而不是在代码运行时加载,所以无法实现条件加载。也正因如此才使得静态分析成为可能。
- export
export
可以导出一个对象中包含的多个属性、方法。
export default
只能导出一个可以不具名的函数。
导出的内容可以使用import
进行引用,也可以直接使用require
进行引用。
- import
//export导出方式的引用方式
import {fn} from 'xxx'
//export default 导出方式的引用方式
import fn from './xxx/xxx'
AMD
Asynchronous Module Definition,异步模块定义。它是一个在浏览器模块化开发的规范,不是原生js的规范,使用AMD规范进行页面开发需要用到对应的函数库,RequireJS
主要有两个JavaScript库实现了AMD规范:require.js和curl.js
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
使用RequireJS实现AMD规范的模块化:用require.config()指定引用路径等,用define()定义模块,用require()加载模块
//定义模块
define('moduleName',['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
})
//引入模块
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});
//自定义模块加载行为
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
//语法
define(id,dependencies,factory)
id 可选参数,模块标识,默认为脚本文件名
dependencies是依赖的模块名称数组
factory是函数,在模块的依赖加载完毕之后,该函数会被调用来定义该模块,因此该模块应该返回一个定义了本模块的object。依赖关系会以参数的形式注入到该函数上,参数列表与依赖名称列表一一对应。
require([module],callback);
第一个参数[module],是一个数组,表示所依赖的模块;
第二个参数callback,加载成功之后的回调函数,当前面指定的模块都加载成功之后,它会被调用。加载的模块以参数形式传入该函数,回调函数内部就可以使用这些模块。
RequireJS主要解决的问题
文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器,js加载的时候浏览器会停止页面渲染,加载文件越多,页面响应时间越长,所以需要异步前置加载
实现js文件的异步加载,避免网页失去响应
管理模块之间的依赖性,便于代码的编写和维护。
CMD
CMD 是另一种js模块化方案,它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范是在SeaJS推广过程中产生的。
因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id;
CMD推崇依赖就近,所以一般不在define的参数中写依赖,而是在factory中写。
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
AMD和CMD的区别?
1.AMD是RequireJS在推广过程中对模块定义的规范化产出,CMD是SeaJS在推广过程中对模块定义的规范化产出
2.对于依赖的模块,AMD是提前执行,CMD是延迟执行
3.CMD推崇依赖就近,AMD推崇依赖前置
UMD通用模块规范
一种整合了CommonJS和AMD规范的方法,希望能解决跨平台模块方案。
运行原理
UMD先判断是否支持Node.js的模块(exports是否存在),存在则使用Node.js模块模式。
再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
参考
Javascript模块化编程(三):require.js的用法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」