概述
今天恰好看了有关模块加载的文章,挺有意思的,以前虽然在写react博客的时候用到了模块加载,但只是粗浅的认识而已,所以今天大致的查阅了一番,总结了一下,记录下来供以后开发时参考,相信对其他人也有用。
浏览器并没有实现
虽然浏览器支持了es6的大部分内容,但是很遗憾,大多数浏览器还并不支持es6的模块加载语法,通过在浏览器的开发者工具查看并没有module对象,也没有import或export方法:
//开发者工具的控制台输入
module //ReferenceError
export //ReferenceError
import //ReferenceError
并且,babel只默认转换新的js语法,而不转换新的API。也就是说,如果要进行模块加载的话,只能借用一些库或者打包工具比如webpack了。
值得一提的是,阮一峰的es6入门里面提到可以用type=module的方法来进行模块加载,但是我在chrome浏览器试了没有成功,mdn也说浏览器并不支持模块加载。(根据stackflow的说法,只有Chrome 61, Firefox 54,MS Edge 16和Safari 10.1支持模块加载。)
module基础
关于es6的模块加载有以下2点需要注意:
- 在模块里面即使不写use strict,它也会自动变成严格模式。
- 并没有module关键字,但是需要使用import和export关键字,并且这些关键字能出现在顶层作用域的任何地方。
//kittydar.js
export {detectCats, Kittydar};
function detectCats(canvas, options) { ... }
class Kittydar { ... }
//main.js
import {detectCats, Kittydar} from "kittydar.js";
当然,也可以在import或export的时候重命名:
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
上面的写法,import的方法都需要和export的方法名字相同,这非常不方便。我们希望默认export一个方法,然后import就可以任意写方法名,写法如下:
let myObject = {
field1: value1,
field2: value2
};
export {myObject as default};
//另一种写法
export default {
field1: value1,
field2: value2
};
es6也支持把目标文件的所有export的方法导入到一个对象里面:
import * as cows from "cows";
//使用的时候通过cows.moo()调用。
如果在一个模块中,先导入然后导出同一个模块,可以像这样简化写:
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
模块加载的机制
es6并定义模块加载的细节,它把这些留给浏览器自己来实现。它只要求以下四件事:
- 解析:浏览器模块源码并检查语法错误。
- 加载:浏览器加载所有import的模块。(至于怎么加载,并没有标准)
- 链接:import的模块并不是复制,而是引用。
- 运行:运用模块中的代码。
静态与动态
js是一门动态的语言,但是模块加载却是静态的,主要表现在以下几个方面:
- 只能在顶层作用域而不能在函数作用域中使用import或export。
- 并不支持循环加载。
- Module是冻结的,你不能在加载的时候引入新的方法。
- 模块加载的时候需要按顺序加载,并不能懒加载或者按需加载。
- 只要一个import的模块报错,整个程序都不能运行。
- 单个模块加载前后没有钩子,不能进行控制。
与以前的模块加载的比较
以前的模块加载技术主要是CommonJS和AMD风格,他们都是用require方法,但是却有着本质的不同,主要表现在:
- CommonJS风格常用于服务器,号称“运行时加载”,它是同步加载的,在浏览器运行的话,会发生很严重的阻塞。另外它的原理是深复制整个模块对象。
- AMD风格常用于浏览器,号称“编译时加载”,在编译时就完成模块加载,所以效率比CommonJS高,它并不加载整个对象,而是加载选用的几个方法,所以可以按需加载。