JavaScript模块化历程
-
工具(浏览器兼容)
-
组件(功能模块)
-
框架(功能模块组织)
-
应用(业务模块组织)
但是经过了长长的后天努力过程JavaScript不断被类聚抽象,以更好的组织业务逻辑。从另一个角度而言,他也道出了JavaScript先天就缺乏的一项功能:模块
虽然W3C组织对HTML5规范推进以及各大厂对规范的大力支持,但是对于JavaScript本身而言,它的规范依然是薄弱的,它还有以下缺陷:
-
没有标准模块系统
-
标准库少(ECMAScript仅定义了部分核心库)
-
没有标准接口(JavaScript几乎没有定义过如WEB服务器数据库操作之类的标准统一接口)
-
缺乏包管理系统
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); // 5 console.log(example.addx(1)); // 6
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