演变过程
早期对于开发者对于'模块化'的实现的几种方式
- 通过文件划分方式
- 所有模块都在全局工作,污染全局作用域
- 命名冲突问题
- 无法管理模块依赖关系
- 命名空间方式
- 将每个模块包括成全局对象的方式实现,减小命名冲突的可能
- 成员在外面还是可以被修改
- 立即执行函数,提供一个私有空间,对于提供给外部的成员,通过挂在到全局对象的方式去实现
- 实现了私有成员的概念
模块化规范的出现
模块化标准 + 模块加载器
CommonJS规范
- 一个文件就是一个模块
- 每个模块都有独立的作用域
- 通过module.exports 导出成员
- 通过require 函数载入模块 CommonJS是以同步模式加载模块,所以在早期浏览器开发中,使用的是结合CommonJS设置的AMD规范,同期的还有淘宝出的 Sea.js + CMD ,设计初衷是为了尽量让写法接近于CommonJS,降低学习成本,后来也被require.js兼容了
AMD 使用起来相对复杂,模块JS文件请求频繁
模块化的最佳实践
- 在Node中一般遵循 CommonJS规范
- 在浏览器中遵循 ES Modules 规范
- ES Modules 是ES6定义的一个最新的模块系统,是最近这几年才被定义的标准,所以存在各种的环境兼容问题
- 随着Webpack 等打包工具的流行,这一规范才逐渐开始普及
- 浏览器也开始逐步支持
深入讨论ES Modules
- 作为规范到底约定了哪些特性和语法
- 如何通过工具和规范在运行环境中兼容性带来的问题
约定的特性和语法
特性
- ESM 自动采用严格模式,忽略 'use strict'
- 每个ESM 都是运行在单独的私有作用域中
- ESM 是通过 CORS 的方式请求外部js 模块
- ESM 的script 标签会延迟执行脚本(网页渲染完成后,再去执行脚本)
语法
-
导入和导出(import/export)的注意事项
- 导出的成员并不是字面量对象,导入的时候与结构也是不相同,只是一个语法而已
- ES Mudules 的导出并不是导出里面的值,而是导出存放的地址,
- 在外部导入之后,并不能去修改
-
导入的用法
路径问题
- import 导入的时候引入模块不能省略.js的拓展名
import { name } from './module.js' - 也是不能去载入index,必须使用完整路径
- ./ 的相对路径也不能使用,不写会被以为是第三方模块, 导入问题
- import {} from 'module.js'只会提取这个模块,不会提取任何成员,简写为
import './module.js' - import * as mod from 'module.js' 提取所有成员 import为导入模块的声明
- 运行时就应该知道需要导入的路径,只能出现在最顶层作用域,并不能出现在判断中 动态导入模块
import('./module.js')返回的是Promise
import('./module.js').then(function(module){ console.log(module) // 模块中导出的成员 })如果在一个模块中导出了一些命名成员,同时导出了一些默认成员
// 两种写法 import {name,age,default as title} from './module.js' import title, { name, age } from './module.js' - import 导入的时候引入模块不能省略.js的拓展名
如何通过工具或方案解决在运行环境中兼容性问题
Polyfill —— Browser ES Module Loader
一个js文件,可以在网页中使用ES module browser-es-module-loader的github
如果IE还不支持Promise,在IE中还需要单独去引入命为Promise的Polyfill
支持ES Module 的浏览器会运行两遍的问题 通过script的属性去解决
<script nomodule>
// 只会在不支持module 的浏览器上去执行
</script>
ESModule in Node
从Node8.5版本之后逐步支持
- ESM 中可以导入CommonJS模块
- CommonJS中不能导入ESM模块
- CommonJS始终只会导出一个默认成员
- 注意 import 不是解构导出解构对象