现代模块化机制要解决的问题如下
- 命名污染,全局污染,变量冲突等基础问题
- 内聚且私有,变量不能被外界污染到
- 怎么引入(依赖)其它模块,怎样暴露出接口给其它模块
- 依赖顺序问题,比如以前的 Jquery 问题
- 循环引用问题,等边界情况
演变过程
- 文件划分,污染全局作用域、产生命名冲突
- 包裹成全局变量,命名空间,但仍然没有私有空间,没有解决模块依赖
- 立即执行函数,私有作用域
模块化规范
- commonjs:规定一个文件就是一个模块,每个模块有单独的作用域,以同步的方式加载模块,node内置的模块系统
服务端模块规范,可以实现服务器端模块重用,拷贝方式是是值拷贝;但加载模块是同步的,只有加载完成后才能执行后面的操作 。
//定义一个module.js文件
var A = function() {
console.log('我是定义的模块');
}
//导出这个模块
//1.第一种返回方式 module.exports = A;
//2.第二种返回方式 module.exports.test = A
//3.第三种返回方式 exports.test = A;
exports.test = A;
//再写一个test.js文件,去调用刚才定义好的模块,这两个文件在同一个目录下
var module = require("./module"); //加载这个模块
//调用这个模块,不同的返回方式用不同的方式调用
//1.第一种调用方式 module();
//2.第二种调用方式 module.test();
//3.第三种调用方式 module.test();
module.test();
// 执行: node test.js
node test.js
// 输出结果:我是定义的模块
- AMD:Require.js,define定义,return导出,通过require加载模块,模块JS加载频繁
可以实现异步加载依赖模块,并且会提前加载 ,require的时候加载;但使用需要先下载 require.js 文件,开发成本高,代码的阅读和书写比较困难。
// 编写一个module1.js文件
// 对象形式定义独立的模块
define({
methodA: function() {
console.log('我是module1的methodA');
},
methodB: function() {
console.log('我是module1的methodB');
}
});
// 编写一个module2.js文件
// 函数返回对象形式定义独立模块
define(function () {
return {
methodA: function() {
console.log('我是module2的methodA');
},
methodB: function() {
console.log('我是module2的methodB');
}
};
});
// 编写一个module3.js文件
// 定义非独立的模块(这个模块依赖其他模块)
define(['module1', 'module2'], function(m1, m2) {
return {
methodC: function() {
m1.methodA();
m2.methodB();
}
};
});
//再定义一个main.js,去加载这些个模块
require(['module3'], function(m3){
m3.methodC();
});
// 在一个html文件中去通过RequireJS加载这个main.js
<script data-main="main" src="require.js"></script>
// 浏览器控制台输出结果:
// 我是module1的methodA
// 我是module2的methodB
- CMD:Sea.js,define定义,export导出
依赖就近,延迟执行;但需要下载seaJS模块 ,模块的加载逻辑偏重 ,依赖 SPM 工具打包。
// cmd.js
define(function(require, exports, module) {
var a = require('./a');
a.doSomething();
// 此处略去 100 行
var b = require('./b'); // 依赖可以就近书写
b.doSomething();
// ...
})
// main.js
require(['cmd']);
- ESmodule
在ES6之前,要想在前端做模块化开发,必须依赖第三方框架来实现,如:requireJS与seaJS。ES6的出现,完全可以取代AMD 、CMD规范和NodeJS支持的CommonJS 规范,浏览器和服务端都支持。
export default A
import A from './moduleA.js'
export class myClass { }
export var a = 'a'
export let b = 'b'
export const c = 'c'
export {
good,
nice
}
import { myClass, a, b, c, good, nice } from './export'
循环加载
- CommonJS的做法是,一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
- ES6根本不会关心是否发生了"循环加载",只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
ESModules
- 基本特性
- 自动采用严格模式,例如this直接打印出来是undefined
- 单独运行在私有作用域中,不会造成全局污染,例如两个module内部变量命名可以相同
- 通过CORS的方式请求外部JS模块,所以只能请求支持CORS的方式的外部地址
- script标签会延迟执行脚本,会等待网页渲染完成之后再执行,这样就不会阻碍网页渲染
<script type="module" src="./domo.js" ></script>
<p>需要显示的文案<p>
- 使用
- 导出
var name = 'ztt';
function hi () {};
class Persen {};
// export {name , hi, Persen}
export {
name as default,
// hi as hello,
}
export default name;
- 导入
// 1、不能省略后缀名、文件完整路径、路径的./
// 2、导入{}或者只写import '...' :只会执行模块,而不是提取成员
import {} from './module.js';
import './module.js';
// 3、导入所有成员到mod对象中
import * as mod from './module.js';
// 4、由于不能直接from变量、并且import只能出现在最顶层,如果需要动态传入则:
import ('./module.js').then(function (module) {})
// 5、同时导出命名成员和默认成员
import { name, age, default as newName } from './module.js'
import newName, { name, age } from './module.js'
// 6、把导入变成导出成员
export { name, age } from './module.js'
- 注意事项
- export 后面不是对象字面量,export defalt 后面是变量或者是值,可以跟对象
- 导出的是引用关系,而不是值
- 对外暴露的引用关系是只读的,不能在模块外部修改成员,外部拿到的是一个常量
-
运行环境兼容问题--调试时使用
- 引入ESmodule Loader
- script加上nomodule属性:避免重复执行模块
- 运行浏览器 browser-sync . -- files **/*.js
- 兼容原理:通过Babel转化
-
In Node.js
- 支持情况:版本8.5以上
- 与commonJS交互:ES可以载入ComJS模块,反过来不能,Com始终只会导出一个默认成员,import不是解构导出对象
- 启动:nodemon --experimental-modules esm.mjs, 或者给package.json加上"type": "module",给ComJS文件改为.cjs
- 在ESmodule使用common.js的对象
import { fileURLToPath } from 'url';
import { dirname } from 'pach';
const __filename = fileURLToPath(import.meta.url);
console.log(__filename);
const __dirname = dirname(__filename);
console.log(__dirname)
- 早期node版本:使用babel兼容
yarn @babel/node @babel/core @babel/preset-env --dev
使用1:
yarn babel-node xxx.js --presets=@babel/preset-env
或者创建.babelrc
使用2: yarn add @babel/plugin-transform-modules-commonjs --dev 或者"preset": ["@babel/preset-env"]"plugins": [ "@babel/plugin-transform-modules-commonjs" ]