概述

今天恰好看了有关模块加载的文章,挺有意思的,以前虽然在写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并定义模块加载的细节,它把这些留给浏览器自己来实现。它只要求以下四件事:

  1. 解析:浏览器模块源码并检查语法错误。
  2. 加载:浏览器加载所有import的模块。(至于怎么加载,并没有标准)
  3. 链接:import的模块并不是复制,而是引用。
  4. 运行:运用模块中的代码。

静态与动态

js是一门动态的语言,但是模块加载却是静态的,主要表现在以下几个方面:

  • 只能在顶层作用域而不能在函数作用域中使用import或export。
  • 并不支持循环加载。
  • Module是冻结的,你不能在加载的时候引入新的方法。
  • 模块加载的时候需要按顺序加载,并不能懒加载或者按需加载。
  • 只要一个import的模块报错,整个程序都不能运行。
  • 单个模块加载前后没有钩子,不能进行控制。

与以前的模块加载的比较

以前的模块加载技术主要是CommonJS和AMD风格,他们都是用require方法,但是却有着本质的不同,主要表现在:

  • CommonJS风格常用于服务器,号称“运行时加载”,它是同步加载的,在浏览器运行的话,会发生很严重的阻塞。另外它的原理是深复制整个模块对象。
  • AMD风格常用于浏览器,号称“编译时加载”,在编译时就完成模块加载,所以效率比CommonJS高,它并不加载整个对象,而是加载选用的几个方法,所以可以按需加载。