一、CommonJS规范
在CommonJS目录下创建三个文件:
- CommonJS/moduleB.js
module.exports = new Date().getTime();
- CommonJS/moduleA.js
const timestamp = require('./moduleB');
console.log('moduleA:::', timestamp);
- CommonJS/index.js
require('./moduleA');
const timestamp = require('./moduleB');
console.log('index:::', timestamp);
其中,CommonJS/index.js引用了moduleA,而moduleA中引用了moduleB,另外,CommonJS/index.js还直接引用了moduleB,可见moduleB是被引用了两次。在这样的ModuleB被两次依赖的情况下,ModuleB是否会被执行两次呢?
通过执行node CommonJS/index.js
我们来看看输出结果:
moduleA::: 1617595616025
index::: 1617595616025
可以看出,虽然ModuleB被依赖了两次,但是输出的时间戳的值是一样的,可见,ModuleB并没有被执行两次。
总结一下,关于CommonJS规范:
1、JavaScript原生并不支持该规范,在没有经过打包工具打包的情况下,它是只可以在Node.js环境中运作的(在浏览器环境下不支持该规范,会报错);
2、CommonJS模块被多次引用时,会保持模块是一个单例,而不会被重复执行多次;
3、用module.exports导出模块,用require引入模块。
二、AMD规范
受到CommonJS模块化规范的启发,浏览器端逐步发展起来了AMD规范。AMD全称为Asynchronous module definition,意为异步的模块定义。正如其名,所有模块默认都是异步加载的,这也是当时为了满足Web开发的需要,因为如果在Web端也像CommonJS规范那样同步加载的话,那么页面在解析脚本文件的过程中可能由于耗时过长,而使得页面暂停响应。
在AMD文件夹下新建4个文件:
- AMD/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test AMD</title>
</head>
<body>
<h2>test AMD</h2>
<script src="https://requirejs.org/docs/release/2.1.16/minified/require.js"></script>
<script src="./index.js"></script>
</body>
</html>
- AMD/index.js
require([
'./moduleA.js',
'./moduleB.js'
], function(moduleA, moduleB) {
console.log('index:::', moduleB);
});
- AMD/moduleA.js
define(function (require) {
const timestamp = require('./moduleB.js');
console.log('moduleA:::', timestamp);
});
- AMD/moduleB.js
define(function (require) {
return new Date().getTime();
});
然后执行npm install -g anywhere
安装静态服务启动工具anywhere,并执行
cd AMD
anywhere -p 80
在80端口上启动静态资源服务器。
然后通过 http://localhost 访问该服务,在Chrome Devtools的console面板上可看到如下输出:
moduleA::: 1617597603392
index::: 1617597603392
这表明同CommonJS规范一样,AMD规范中,模块被多次引用时,也是单例的,不会被执行多次。
接下来,我们把AMD/index.js文件的内容改成如下,即去除AMD/index.js对moduleB的依赖:
require([
'./moduleA.js',
], function(moduleA) {
});
然后,我们打开Chrome Devtools的Network面板,把网络模式Slow 3G(如下图)。
然后刷新一下页面,查看到各JS文件的加载顺序如下图:
可见,ModuleA和ModuleB这两个AMD模块确实是异步加载的,且因为ModuleA所依赖的ModuleB,也是在ModuleA加载完之后才去异步地加载ModueB的。
AMD异步加载的核心原理是使用了JSONP来加载模块。
总结一下:
1、AMD规范是运行在浏览器端的,不能运行在Node.js环境中。
2、AMD默认是支持模块异步加载的。因为如果在Web端也像CommonJS规范那样同步加载的话,那么页面在解析脚本文件的过程中可能由于耗时过长,而使得页面暂停响应。
3、AMD模块被多次引用时,会保持模块是一个单例,而不会被重复执行多次;
4、用define(function (require) {})
(匿名模块,以文件名为模块名) 或者 define('模块id', ['依赖数组'], function([依赖数组]){});
(具名模块)定义模块,用require(['./xxx.js'], function(xxx) {})
引入模块,其中第二个参数是回调。
三、UMD规范
UMD全称是Universal Module Definition(通用模块规范),它是由社区想出来的一种整合了CommonJS和AMD两个模块定义规范的方法。所以,严格意义上说,它并不是一个独立的模块化规范。其基本原理是用一个工厂函数来统一不同的模块定义规范。
下面基于UMD规范写一个简单的例子(模块内容只是纯演示,该内容不是一个值得封装成模块的东西):
- UMD/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test 111</title>
</head>
<body>
<button id="btn">按钮</button>
<div id="container"></div>
<script src="./jquery.js"></script>
<script src="./moduleA.js"></script>
<script src="./index.js"></script>
</body>
</html>
- UMD/index.js
window.moduleA.init();
- UMD/jquery.js
略去,从网上下载一个jQuery.js文件放在这里即可。
- UMD/moduleA.js
((global, factory) => {
//如果 当前的上下文有define函数,并且AMD 说明处于AMD 环境下
if (typeof define === 'function' && define.amd) {
// 检查AMD是否可用
define(['./jquery'], factory);
} else if (typeof exports === 'object') {
// 检查CommonJS是否可用
modules.exports = factory(require('./jquery'));
} else {
// 两种都不能用,把模块添加到JavaScript的全局命名空间中
global.moduleA = factory(global.jQuery);
}
})(this, ($) => {
//本模块的定义
function init() {
var $container = $('#container');
var count = 0;
var text = ['关', '开'];
document.getElementById('btn').addEventListener('click', function() {
count += 1;
$container.html(text[count % 2]);
});
}
return {
init: init
}
})
四、ESModule
不管是CommonJS还是AMD,都是由语言上层的运行环境中实现的模块化规范,模块化规范由环境自己定义。但是ESModule是在语言层面实现的。
先创建如下5个文件:
- ESModule/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test ESModule</title>
</head>
<body>
<h2>test ESModule</h2>
<script src="./dist/index.bundle.js"></script>
</body>
</html>
- ESModule/index.js
import './moduleA.js';
import timestamp from './moduleB.js';
console.log('index:::', timestamp);
- ESModule/moduleA.js
import timestamp from './moduleB.js';
console.log('moduleA:::', timestamp);
- ESModule/moduleB.js
export default new Date().getTime();
然后安装依赖:
yarn add webpack webpack-cli @babel/core babel-loader @babel/preset-env
并修改package.json中的scripts为:
"scripts": {
"build": "webpack"
},
执行npm run build
进行打包。然后执行anywhere -p 80
启动静态服务器。
然后通过 http://localhost 访问该服务,在Chrome Devtools的console面板上可看到如下输出:
moduleA::: 1617601623808
index::: 1617601623808
不过,由于兼容性的问题,ESModule目前在浏览器环境下还不能直接使用,需要进行打包编译。
Babel会将ES6中的import
和export
编译成CommonJS规范的require
和exports
。如下图所致,左边是编译前的代码,右边是编译后的代码(可以到www.babeljs.cn/中体验测试):
但是我们知道,CommonJS是无法运行在浏览器端的,怎么让它能够变成能运行在浏览器端的代码呢?这就是打包要做的事情了。
那么Node.js中是如何做到能运行CommonJS模块的呢?
例如,对于这样一段CommonJS代码:
require('./moduleA');
const timestamp = require('./moduleB');
console.log('index:::', timestamp);
它是这样处理的,通过fs模块读到模块的代码字符串,然后在其头部拼接上function(require, module, exports) {,在其尾部拼接上},最后将拼接得到的字符串放入vm.runInNewContext
API中执行:
const str = 'require('./moduleA');const timestamp = require('./moduleB');console.log('index:::', timestamp);'
const functionWrapper = [
'function(require, module, exports) {',
'}'
];
const result = functionWrapper[0] + str + functionWrapper[1];
const vm = require('vm');
vm.runInNewContext(result);
vm是Node.js中的一个内置模块,其runInNewContext方法的作用相当于new Function(codeStr)()
(参见这里)或者 eval(codeStr)
(参见这里),关于runInNewContext方法的用法可参见Node.js的官方文档。
此外,还有人可能用过CMD规范,但因为它毕竟当前应用已经不那么多了,这里就不介绍了,感兴趣的可以自行了解下。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!