早期将js代码放入不同的文件里面,通过多个srcipt
标签来加载他们
<script src='./a.js'></script>
虽然每个代码块出在不同的文件中,但最终所有的js变量还是会处在全局作用域下,就需要额外注意由于作用域变量提升所带来的问题。如果代码之间有依赖关系,则需额外注意脚本加载的顺序。
为解决这样的问题,我们需要将这些脚本文件模块化:
- 每个模块有自己的变量作用域
- 不同模块之间保留相互导入和导出的方式方法。
commonJs规范初探
nodejs就是一个基于V8引擎,事件驱动I/O的服务端js运行环境,在09年刚推出时,就实现了一套CommomJs的模块化规范
每个js就是一个模块(module,每个模块内部使用require
函数和module.exports
对象对模块进行导入导出
在不同的模块加载moduleB两次,会得到相同的结果,这说明他保证了模块单例。
commonJs是一个同步加载模块的模块化规范,每当一个模块require一个子模块时,都会停止当前模块的解析直到子模块读取解析并加载
适合web开发的AMD模块化规范
asynchronous module definition 意为 异步模块定义,因为如果在浏览器中也使用同步加载,那么页面在解析脚本文件的过程中可能会使页面暂停响应。
AMD规范举例
// index.js
// require函数第一个参数是 入口模块的依赖列表, 第二个参数作为回调函数依次传入前面依赖的导出值,不同于CommonJS的是,这个回调函数的返回值即时模块导出结果
require(['moduleA','moduleB'], function (moduleA, moduleB){
console.log(moduleB)
})
// moduleA.js
define(function(require){
var = require('moduleB')
setTimeOut(() => console.log(m), 1000)
})
// moduleB.js
define(function(require){
var m = new Data().getTime();
return m
})
Nodejs里直接通过node index.js
可查看输出结果,在web端我们需要一个html文件,同时在里面加载这个入口模块, 如果想使用AMD规范, 还需要在页面中添加符合AMD规范的加载器脚本
<script src="/require.js"></script>
直接起到服务器里命令
python -m SimpleHTTPServer 8080
从结果上来说 AMD和CommonJs一样,完美解决了变量作用域和依赖关系之类的问题,但是这种AMD默认异步, 在回调函数中定义模块内容, 相对来说使用就麻烦些。
同时, AMD的模块不能直接运行在node端,因为内部的define函数,require函数都必须配合在浏览器中加载require.js
这类ADM库才能使用。
能同时被CommonJs规范和AMD规范加载的UMD模块
有时候我们希望写的模块能同时运行在浏览区端和NodeJs里,使用UMD(universal module definition)作为一种同构的模块化解决方案,在一个地方定义模块内容,同时兼容ADM和CommonJS规范
需要判断一下这些模块化规范的特征值,判断出当前究竟在哪种模块化规范的环境下,然后把模块内容用检测出的模块化规范的语法导出
// 写法不固定
(function(self, factory){
if(typeof module === 'object' && typeof module.exports === 'object'){
module.exports = factory()
} else if ( typeof define === 'function' && define.amd){
define(factory)
} else {
// 什么环境也不是,直接挂在全局对象上
self.umdModule = factory()
}
})( this, factory){
return function(){
retrun Math.randow()
}
}
ESModule规范
es6之后,js有了语言层面的模块化导入导出关键词和语法以及匹配的esModule规范。
示例:
// index.js
import './moduleA';
import m from './moduleB'
console.log(m)
//moduleA.js
import m from './moduleB'
setTimeOut(()=> console.log(m), 1000)
// moduleB.js
var m = new Data().getTime();
export default m
// import { var1 as customVar } from './moduleA'
// 以下用法合理 因为commonJs导出的就是个对象
const { var1 = 1} = require('./moduleA')
const { [test]: var1: a} = require('./moduleA')
每个js运行环境都有一个解析器,他的作用就是用ECAMScript规范去解释js语法,也就是处理和执行语言本身的内容。在解析器上层,每个运行环境都会在解释器的基础上封装一些环境相关的API。
esModule是高版本工具,需要使用babel转义,对于模块化的import
和export
关键字,bebal最终会将他编译为包含require和export的commonJs规范,这就造成了另外一个问题, 这些关键字编译之后无法运行在浏览器中, 我们还需要一个步骤 打包,比如打包工具webpack/rollup,这样编译工具babel和他们的区别和作用就清楚了
- 打包工具主要处理的是js不同版本间模块化的区别
- 编译工具主要是处理JS版本间语义的问题
如果使用了ESModule:必须使用webpack和babel
如果是AMD或CommonJs:只用webpack
网友评论