写在前面
这是ES6笔记的最后一篇内容,也是唯一一个将来才能使用的特性
将来是什么时候?
或许是HTTP2普及的时候。但更大的可能是将来也“不能用”(还是只能在构建工具中用,仅存在于“编译期”)
AMD/CMD,一点扩展知识如下: CommonJS是一套理论规范(比如js的理论规范是ES),而SeaJS, RequireJS都是对CommonJS的Modules部分的具体实现 CommonJS是面向浏览器外(server端)的js制定的,所以是同步模块加载,SeaJS是CommonJS的一个实现,而RequireJS虽然也是对CommonJS的一个实现,但它是异步模块加载,算是更贴近浏览器的单线程环境 总结:CommonJS的Modules部分提出了模块化代码管理的理论,为了让js可以模块化加载,而RequireJS, SeaJS等各种实现可以称为模块化脚本加载器 CMD:Common模块定义,例如SeaJS AMD:异步模块定义,例如RequireJS 都是用来定义代码模块的一套规范,便于模块化加载脚本,提高响应速度 CMD与AMD的区别: CMD依赖就近。便于使用,在模块内部可以随用随取,不需要提前声明依赖项,所以性能方面存在些许降低(需要遍历整个模块寻找依赖项目) AMD依赖前置。必须严格声明依赖项,对于逻辑内部的依赖项(软依赖),以异步加载,回调处理的方式解决
(引自JS编程常识)
如果关注过JS模块化,应该清楚这三者混乱的关系,ES6模块希望通过标准来结束这种混乱
module引入了模块作用域,特点如下:
'use strict';
import/export {api as newApi}
,引入时重命名主要解决命名冲突,导出时重命名可以实现别名($
和jQuery
)import/export
,且不能再条件语句中使用总结:推进严格模式;兼容CommonJS和AMD;只是单纯的静态模块机制,没有解决按需加载之类的问题
引入/导出时重命名,示例如下:
// 引入时重命名,解决命名冲突
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";// 导出时重命名,实现别名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
import {api1, api2...} from 'xxx.js'
语法,特点如下:
xxx.js
是完整加载的,部分引入只是作用域控制)xxx.js
还有import
语句,会深度优先加载执行default
就是export
对象),import api from 'xxx.js'
等价于import {default as api} from 'xxx.js'
import * as apis from 'xxx.js'
,*
表示xxx.js
中export
的所有东西,把xxx.js
中导出的所有东西整合到apis
对象中,通过apis.xx
访问总结:加载机制类似于CSS的@import
,处理循环依赖的方式也类似;同样兼容CommonJS和AMD
作用域层面支持部分引入,有用,但意义不大,配合构建工具编译时“剪枝”(tree shaking)更好一些
export {api1, api2...}
语法,特点如下:
export
export
,但要保证api名称无重复,名称重复可能会出错export default api
或者export {api as default}
export {api1, api2...} from 'xxx.js'
等价于import + export
,但export from
不会在当前模块作用域引入各个api变量(引入后直接导出,无法引用)export
导出的api列表必须是字面量形式,不能遍历数组导出数组元素总结:加载时整理export
列表,所以可以在外层任何位置export
;支持聚合,从各个第三方模块抽出一部分整合起来;静态限制,不允许动态导出
示例如下:
// 默认导出
let myObject = {
field1: value1,
field2: value2
};
export {myObject as default};
// 等价于
export default {
field1: value1,
field2: value2
};// 聚合导出
// 导入"sri-lanka"并将它导出的内容的一部分重新导出
export {Tea, Cinnamon} from "sri-lanka";
// 导入"equatorial-guinea"并将它导出的内容的一部分重新导出
export {Coffee, Cocoa} from "equatorial-guinea";
// 导入"singapore"并将它导出的内容全部导出
export * from "singapore";
ES6标准没有写明具体模块加载机制,交由最终实现来定,但明确规定了模块执行机制,分为4个步骤
递归加载所有被import
的东西,具体怎么加载,没有写明,完全交由最终实现来定
创建模块作用域,并把所有被import
进来的东西塞进作用域
如果import
出错,就会触发错误,具体行为未知(因为还没有浏览器已经走过了第2步)
执行每一个模块的所有语句,此时遇到import/export
就忽略掉,因为模块相关的处理已经结束了
import/export
,不能在条件语句中使用,也不能在函数作用域用export
的标识符必须是字面量形式(要在源码中有对应的声明),不能遍历数组再导出一堆东西hack
模块对象来添加polyfill
风格的新特性import
来按需懒加载的语法import
模块产生的错误没有对应的恢复机制。如果有一个模块无法加载或连接,所有的模块都不会执行,而且无法捕获import
错误因为存在这些限制,所以可能在HTTP2普及后,ES6模块机制还是不能在浏览器兴起,像CSS的@import
一样,能用,但都不愿意用
在HTTP1.1的环境下,为了减少HTTP请求数量,所有模块化方案最终都依赖构建工具整合出单一文件
但HTTP2带来了一些变革,也许能够改变这种工程化流程,比如多路复用流(Multiplexed stream)和服务器端推送:
Http2连接可以承载数十或数百个流的复用,多路复用意味著来自很多流的数据包能够混合在一起通过同样连接传输,两列不同火车被混合在一起传输,当到达终点时,它们又被拆开组成两列不同的火车。 客户端请求一个资源X,服务器端判断也许客户端还需要资源Z,在无需事先询问客户端情况下将资源Z推送到客户端,客户端接受到后,可以缓存起来以备后用。
(引自Http 2.0协议简介)
多路复用流抹平了文件合并的优势,服务端推送有助于解决深度import
问题,所以ES6模块可能会在浏览器环境兴起
HTTP2对于模块化进程有重要意义,为在生产环境保持模块化提供了机会,JS、CSS甚至其它资源都可能迎来真正的模块化
P.S.关于HTTP2的更多细节,请查看https://github.com/bagder/http2-explained
As the various milestones of the roadmap are completed, browsers will be able to implement them. See the following trackers for the current status of the main browsers: IE/Edge: Under Consideration Firefox: In progress Chrome: In progress Webkit: Meta Bug
(引自https://github.com/whatwg/loader)
关于ES6模块加载器的更多信息请关注这个repo,ES6规范没有说明加载的具体实现,所以浏览器都卡在了加载器的实现上