前端模块规范简介
模块化的好处:
-
避免命名冲突(减少命名空间污染)
-
更好的分离, 按需加载
-
更高复用性
-
高可维护性
1.CommonJS
1.1 语法风格
//Math.js
module.exports = {
'add': function(a, b) {
return a + b;
}
}
//main.js
const Math = require('./Math');
console.log(Math.add(2, 3));
console.log('done');
-->node main
5
done
由上面例子可以看出,CommonJS中模块是同步加载的
1.3 动态加载
//main.js
const Math = require('./Ma' + 'th');//动态拼接
CommonJS支持动态加载模块。因为conmonJs是同步的,执行到这一行时,才会加载模块。
浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量。
-
module
-
exports
-
require
-
global
可以使用工具进行转换,例如:Browserify
1.5 其他特点
-
所有代码都运行在模块作用域,不会污染全局作用域。
-
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
-
模块加载的顺序,按照其在代码中出现的顺序。
-
CommonJS模块的加载机制中,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
-
适合服务器端
小测验
//Math.js
let counter = 1;
let addCounter = function () {
counter++;
};
module.exports = {
counter,
addCounter
};
console.log('Math init');
//main.js
const Math = require('./Math');
Math.addCounter();
console.log(Math.counter); // position A
Math.counter = 5;
const Math2 = require('./Math');
console.log(Math2.counter); // position B
//位置A 和 位置B的值分别是什么?
CommonJS是主要为了JS在后端的表现制定的,他是不适合前端的。
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。
RequireJS实现了AMD规范。下面以RequireJS为例,了解一下AMD规范。
2.1 语法风格
//Math.js
define([], function(){
return {
'add': function(a, b) {
return a + b;
}
}
})
//main.js
require.config({
paths : {
"math" : "Math"
}
});
require(['math'], function (math) {
console.log(math.add(2, 3));
});
console.log('done');
//done
//5
由上面的例子可以看出,require命令是异步执行的
2.3 动态加载
2.4 依赖前置,提前执行
3.CMD(Common Module Definition)
CMD是SeaJS 在推广过程中对模块定义的规范化产出。
CMD规范和 AMD 很相似,尽量保持简单,并与 CommonJS 规范保持了很大的兼容性。
3.1 语法风格
// CMD
define(function(require, exports, module) {
var a = require('./a');
a.doSomething();
//...
var b = require('./b'); // 依赖可以就近书写
b.doSomething();
// ...
require.async('./c',function(c){ //支持异步加载
c.doSomething();
});
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething();
//...
b.doSomething();
//...
})
1) 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible(尽可能的懒加载,也称为延迟加载,即在需要的时候才加载)。
2) CMD 推崇依赖就近,AMD 推崇依赖前置。虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢依赖前置的写法,也是官方文档里默认的模块定义写法。
3.3 推荐链接
二、Module
ES中Module的特点
-
浏览器,服务器通用
-
静态加载
1. 基本语法
1.1 export
一个模块就是一个独立的文件。export
关键字用来输出模块内部的某个变量 。可以输出变量,函数或类。
// test.js
export var name = 'zhangsan';
export var age = 18;
// test.js
var name = 'zhangsan';
var age = 18;
export { name, age };
as
var name = 'zhangsan';
var age = 18;
export {
name as personName,
age
};
export
命令规定的是对外的接口
// 报错
export 1;
// 报错
var m = 1;
export m;
语句输出的接口,与其对应的值是动态绑定关系
//m.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 1000);
//main.js
import { foo } from './m1.js';
console.log(1, foo);
setTimeout(function () {
console.log(2, foo);
}, 1000);
<!DOCTYPE html>
<html>
<script type="module" src='./main.js'></script>
<body></body>
</html>
//console
1 "bar"
2 "baz"
命令可以出现在模块的任何位置,只要处于模块顶层就可以。
1.2 import
其他 JS 文件通过import
命令加载模块。大括号里面的变量名,必须与被导入模块(test.js
)对外接口的名称相同。
// main.js
import { name, age } from './test.js';
function showName() {
console.log(name);
}
//zhangsan
命令输入的变量都是只读
import {a} from './xxx.js'
a = {}; // Assignment to constant variable.
a
是一个对象,改写a
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
*
// main.js
import * as test from './test.js';
function showName() {
console.log(test.name);
}
//zhangsan
test.name = 'yun';
//Cannot assign to read only property 'name' of object '[object Module]'
//但如果是对象,可以修改对象的属性。
命令具有提升效果
foo();
import { foo } from 'my_module';
import
是静态执行,所以不能使用表达式和变量
// 报错
import { 'f' + 'oo' } from 'my_module';
语句会执行所加载的模块,因此可以有下面的写法。仅仅执行lodash
import 'lodash';
import
语句是 Singleton 模式。
1.3 单例模式解读
//counter.js
export let counter = 1;
export function addCounter(){
counter++;
}
console.log('counter init');
//main.js
import {counter, addCounter} from './counter';
console.log('main:' + counter);
addCounter();
console.log('main:' + counter);
//counter init
//main:1
//main:2
//从这例子中可以看出和CommonJS的区别
另一个例子
//main2.js
import {counter, addCounter} from './counter';
console.log('main2:' + counter);
addCounter();
console.log('main2:' + counter);
<!DOCTYPE html>
<html>
<head>
<title>module test</title>
<script type="module" src='main.js'></script>
<script type="module" src='main2.js'></script>
</head>
</html>
//console
counter init
main:1
main:2
main2:2
main2:3
Singleton 模式。
1.4 export default
使用export default
可以不用关注输出模块中的变量名。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
命令可以为该匿名函数指定任意名字。import
// export-default.js
function foo() {
console.log('foo');
}
export default foo;
export default
的本质,就是输出一个叫做default
的变量或方法。
imort something from ..
.的本质,就是
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
// 正确
export default 42;
// 报错
export 42;
export default
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
export
和import
语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo
和bar
实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo
和bar
// 接口改名
export { fooName as newName } from 'my_module';
//具名接口改为默认接口
export { foo as default } from './someModule';
//接口也可以改名为具名接口
export { default as ES } from './someModule';
//calculator.js
export function add(a, b) {
return a + b;
}
//calculatorPlus.js
export * from './calculator.js';
export function multiply(a, b) {
return a * b;
}
//main.js
import * as cal from './calculatorPlus.js';
cal.add(2, 3);//5
cal.multiply(2, 3);//6
1.7 import()
ES2020 (ES11)新增了 动态import
特性,解决了import动态加载,和不能写在代码块中的问题。
import()
import(a + '.js')
.then(...);
import(f())
.then(...);
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
2.1 ES模块和CommonJS模块的差异
-
import
和export
是关键字,require
不是。 -
CommonJS 模块输出的是一个值的拷贝,ES 模块输出的是值的引用。(详情见上方【单例模式解读】)
-
CommonJS 模块是运行时加载,ES 模块是编译时输出接口。 因为 CommonJS 加载的是一个对象(即
module.exports
属性),该对象只有在脚本运行完才会生成。而 ES 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。 -
this
指向不同。ES 模块之中,顶层的this
指向undefined
;CommonJS 模块的顶层this
指向当前模块,这是两者的一个重大差异。
2.2 循环加载
CommonJS中的循环加载
// a.js
let bar = require('./b').bar;
console.log('bar:', bar);
exports.ada = 'ada';
// b.js
let ada = require('./a').ada;
exports.bar = 'bar';
setTimeout(function () {
console.log('ada:', ada);
}, 0);
执行结果:
-->node a
bar: bar
ada: undefined
ES Module中的循环加载
// a.js
import {bar} from './b.js';
console.log('a.js', bar);
export let ada = 'ada';
// b.js
import {ada} from './a.js';
//console.log(ada);
//此处访问ada会报错
setTimeout(function () {
console.log('b.js', ada);
}, 0);
export let bar = 'bar';
<!DOCTYPE html>
<html>
<head>
<title>module test</title>
<script type="module" src='a.js'></script>
</head>
</html>
执行结果:
//console
a.js bar
b.js ada
解读:
因为CommonJS 模块输出的是一个值的拷贝,当拷贝的动作发生时,ada值为undefined,之后模块a中对ada进行赋值已经不会影响到模块b中保存的ada。
- 点赞
- 收藏
- 关注作者
评论(0)