JavaScript模块化历程
JavaScript发展变迁大概是一下几个步骤:
工具(浏览器兼容)
组件(功能模块)
框架(功能模块组织)
应用(业务模块组织)
但是经过了长长的后天努力过程JavaScript不断被类聚抽象,以更好的组织业务逻辑。从另一个角度而言,他也道出了JavaScript先天就缺乏的一项功能:模块
虽然W3C组织对HTML5规范推进以及各大厂对规范的大力支持,但是对于JavaScript本身而言,它的规范依然是薄弱的,它还有以下缺陷:
没有标准模块系统
标准库少(ECMAScript仅定义了部分核心库)
没有标准接口(JavaScript几乎没有定义过如WEB服务器数据库操作之类的标准统一接口)
缺乏包管理系统
CommonJS规范
CommonJS规范的提出,主要为了弥补JavaScript没有标准的缺陷,以达到想Python、Ruby和Java具备开发大型应用的基础能力,而不是停留在小脚本程序的阶段。
它的使用和定义也非常简单
CommonJS的模块规范
1 模块定义
// example.js
var x =5;
var addx = function (value){
return value + x;
}
module.exports.x = x;
module.exports.addx = addx;
定义关键字是exports这个module是什么意思呢:在CommonJS定义了每个文件都一个模块,也就是说module代表文件本身
2 模块引用
var example = require('./example.js'); console.log(example.x); //
console.log(example.addx(1)); //
CommonJS模块特点如下:
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。
3 看图
用一个图很明显的看出它们之间的引用关系
模块的实现
在Node中引入模块,需要经历3个步骤
1、路径分析
2、文件定位
3、编译执行
在Node中模块分为两类:一类是Node提供的模块,称为核心模块;另一类是用户编写的模块称为文件模块。
ES6 Module
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readFile } = require('fs'); // 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面代码的实质是整体加载fs
模块(即加载fs
的所有方法),生成一个对象(_fs
),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export
命令显式指定输出的代码,再通过import
命令输入。
与CommonJS思想类似
1 模块定义
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
上面代码是profile.js
文件,保存了用户信息。ES6 将其视为一个模块,里面用export
命令对外部输出了三个变量。
export
的写法,除了像上面这样,还有另外一种。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958; export {firstName, lastName, year};
通常情况下,export
输出的变量就是本来的名字,但是可以使用as
关键字重命名。
function v1() { ... }
function v2() { ... } export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
注意规范:
export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 报错
export 1; // 报错
var m = 1;
export m;
上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量m
,还是直接输出1。1
只是一个值,不是接口。正确的写法是下面这样。
// 写法一
export var m = 1; // 写法二
var m = 1;
export {m}; // 写法三
var n = 1;
export {n as m};
function
和class
的输出,也必须遵守这样的写法。
// 报错
function f() {}
export f; // 正确
export function f() {}; // 正确
function f() {}
export {f};
另外,export
语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
2 模块引用
// a.js
let a = 18;
let funcA = function () {
console.log('module A')
}; // 提供一个对外的借口并输出a变量和funcA方法
export {a, funcA}
引用
import {a, funcA} from "./a"; // 通过import 引入里面的变量和方法
console.log(a);
console.log(funcA());
引用的时候可以设置别名
import {a as aValue, funcA} from "./a"; // 通过import 引入里面的变量和方法
console.log(aValue);
console.log(funcA());
3 export default命令
从前面的例子可以看出,使用import
命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default
命令,为模块指定默认输出。
本质上,export default
就是输出一个叫做default
的变量或方法,然后系统允许你为它取任意名字
// export-default.js
export default function () {
console.log('foo');
}
然后你可以随便命名
// import-default.js
import customName from './export-default';
customName(); // 'foo'
但是正常引入模块的情况下你必须指定你要引入的变量或者方法,像下面一样:
import {funcA} from "./a.js"; // 这里你在引用的时候你必须知道a.js中定义的方法的名字
比较下正常和export default不同
// 第一组
export default function crc32() { // 输出
// ...
} import crc32 from 'crc32'; // 输入 // 第二组
export function crc32() { // 输出
// ...
}; import {crc32} from 'crc32'; // 输入
Module 的加载实现
上面是模块的语法,接下来看看如何在浏览器和Node中加载ES6模块
浏览器加载ES6模块
在 HTML 网页中,浏览器通过<script>
标签加载 JavaScript 脚本
<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script> <!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>
上面代码中,由于浏览器脚本的默认语言是 JavaScript,因此type="application/javascript"
可以省略。
同样如果你的目录结构是这样的:
你在本地安装了node_js模块vue的话同样可以直接通过script标签引用
浏览器加载ES6模块
浏览器加载 ES6 模块,也使用<script>
标签,但是要加入type="module"
属性。
<script type="module" src="./foo.js"></script>
浏览器对于带有type="module"
的<script>
,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>
标签的defer
属性。
<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>
ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致。
<script type="module">
import utils from "./utils.js"; // other code
</script>
参考资料:http://es6.ruanyifeng.com/#docs/module