文章输出主要来源:拉勾大前端高新训练营(链接) 与 阮一峰老师电子书ECMAScript 6 入门。小哥哥小姐姐请不要嫌弃啰嗦,下面肯定都是干货。
1. 模块化介绍
ES Module之前,JavaScript本身不存在模块体系,实现模块化的方式基本为:
- Node.js中: 内部实现了CommonJS规范
- 浏览器:CommonJS加载模块为同步方式因此无法再浏览器使用,因此提出新的方案AMD与CMD,对应实现的库分别为require.js与sea.js
ES6之后推出了官方的模块体系,使得js可以原生支持模块化,目前大多在服务端node.js中使用CommonJS方案,在浏览器中使用ES Module方案。
2. ES Module特点
ES Module在浏览器中原生使用方式:
通过在script标签上添加type属性为module代表它遵循ES Module规范
特点:
-
使用ES Module默认会开启严格模式
<script> console.log(this) // 非ES Module:打印全局Window对象 </script> <script type="module"> console.log(this) // ES Module: 打印undefined </script> -
每个ES Module 都运行在单独的私有作用域中,其中定义的变量不会影响全局
<script type="module"> var tom = 'tom'; console.log(tom) // tom </script> <script type="module"> console.log(tom) // 报错:Uncaught ReferenceError: tom is not defined </script> -
ES Module通过
<script type="module" src="xxx.js">在页面器中加载js文件是通过CORS方式请求JS文件的,因此如果请求了跨域的js文件则会报错 -
ES Module默认对脚本执行进行延迟,与使用
defer属性作用一致,都会在页面内容加载完后再执行脚本
3. 导入import与导出export
以下ESModule简称为ESM,由于ESM中的变量的声明都是在私有的作用域中,因此在一个模块中定义的内容需要通过export的方式导出出去才能被外部的模块获取到。一个模块想要使用其他模块中的内容,则需要使用import的方式进行导入。
ESModule中的导出支持以下几种方式:
-
通过
export 变量声明/函数声明/类声明等方式导出变量// app.js export const name = 'tom'; export function sayHello() { console.log('hello world') } export class Person { constructor(name, age) { this.name = name || 'tom'; this.age = age || 18; } } // module.js import { name, sayHello, Person } from './module.js'; console.log(name); sayHello(); console.log(new Person()) -
单独使用export同一导出
const name = 'tom'; function sayHello() { console.log('hello world') } class Person { constructor(name, age) { this.name = name || 'tom'; this.age = age || 18; } } export { name, sayHello, Person } -
导出重命名:通过
oldname as newNmae的方式进行导出重命名,导入模块重命名也是同样的方式// module.js const name = 'tom'; export { name as catName, } // app.js import { catName } from './module.js'; console.log(catName); -
导出
default默认模块与导入默认模块都有两种方式,一种是通过重命名的方式,一种为专门的默认导出与默认导入语法:// module.js const name = 'tom'; export { name as default, // 通过重命名方式导出默认模块 } // module.js const name = 'tom'; export default name; // 通过默认导出方式导出默认模块 // app.js import { default as catName } from './module.js'; // 重命名方式导入 console.log(catName); // app.js import catName from './module.js'; // 默认导入 console.log(catName);
注意事项:
-
通过
export {name, sayHello}的方式与import {name, sayHello} from 'module.js'的方式进行导入与导出,其中的{}是固定的语法,而不是ES6中的对象简写与对象结构 -
通过导出与导入的内容是模块的引用,例如import 的name,就算name是个字符串,那么导入后的name也是定义的那个name变量地址的引用,实际内容都是同一个内存空间的值。
-
通过导入的变量都是只读的,如果是基础类型变量,则无法进行重新赋值,如果是引用类型,无法更改变量指向
ES Module中的导入import
-
import xx from 'path'中 from后面跟的需要是完整的路径名,不能省略.js等后缀或index.js,且path需要为绝对路径或相对路径,例如相对路径需要添加./否则将会被当做第三方模块。注:这里所说完整路径名称是在原生情况下,如果利用了打包工具等则可以设置省略后缀或
index.js -
除了完整的路径,还可以使用url形式引入包,例如
import xxx from 'http://xxxx.com/module.js' -
仅加载模块:
import {} from './module.js'可以仅仅加载模块并不提取其中的内容,简写方式为import 'moudle.js' -
导入模块所有内容:
import * as mod from './module.js'即可导入模块中所有成员,并将其组织为对象mod,(可以自己随意命名) -
动态导入:ES Module提供了全局的
import(path)函数,返回一个promise,参数为一个路径,即可实现动态导入内容,当模块加载成功,即可自动执行其.then方法.例:
import('./module.js').then(module => { const {name} = module; console.log(name); //tom }) -
同时提取默认成员与具名成员:
- 重命名方式:
import {name, default as age} from './moudle.js' - 通过逗号分隔并自定义默认导出名称:
import age, { name } from './module.js'
- 重命名方式:
export与import复合写法
通过export { module1, module2 } from './module.js'的方式即可实现将export与import进行合并,简化书写方式。
export { module1, module2 } from './module.js
// 等价于
import {module1, module2} from './module.js'
export {module1, module2}
重命名与整体导出
// 导出模块也可以重命名,例:
export {module1 as m1, module2 as m2} from './module.js'
// 对应的导入即为
import { m1, m2} from './modules/index.js'
// 整体导出
export * from './module.js'
ES2020补充写法:
// ES2020之前以下写法没有对应的复合写法
import * as mod from './module.js'
export {mod}
// ES2020补充了这种以下写法,等价于上方写法
export * as mod from './module.js'
4. ES Moudle在浏览器中的兼容方案
-
ES Module在老版本浏览器中无法原生支持,可以通过打包工具或构建工具配合babel等将es 6代码转换为es5或更低版本进行兼容。
-
或通过使用
browser-es-module-loader插件进行polyfill,在老版本浏览器中兼容ES Module,通过nomodule属性标记插件代码,避免其在支持ES Module的浏览器中重复执行,导致模块代码被执行两次的bug。例:
<!-- promise polyfill 浏览器如果不支持promise则需要使用promise-polyfill--> <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script> <!--browser-es-module-loader插件中的第一个文件,运行在浏览器端的babel插件,可以将es6代码转换为es5--> <script nomodule src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script> <!--browser-es-module-loader插件中的第二个文件,核心插件:读取模块中的代码,转交给babel--> <script nomodule src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script> <script type="module"> import {name} from './module.js'; console.log(name); </script>注意:
此方式可用于测试时使用,不推荐生产中进行使用,因为这种方法原理还是通过babel进行解析脚本,且在线进行动态解析,性能较差。
5. Node中的ES Module
Node中ES Module的原生支持:
Node8.5以上版本已通过实验特性原生支持ES Module,不过模块命名需要以.mjs为后缀名,且运行时需要指定--experimental-modules选项。或者在node 比较新的版本中(本地使用12.16.3),可以通过package.json中指定"type": "module"即可使用正常的.js作为文件后缀,此时如需使用CommonJS则后缀需要变为.cjs
例:
// module.mjs
const name = 'tom';
const age = 18;
export {
name,
age
}
// index.mjs
import { name, age } from './module.mjs'
console.log(name, age)
通过node --experimental-modules index.mjs即可运行index.mjs
Node中内置模块的成员官方做了兼容,可以通过单独导入或默认导入的方式进行使用,第三方模块如果没做兼容,可能就只能使用默认导入的方式进行使用
// 默认导入内置模块
import fs from 'fs';
fs.writeFileSync('./foo.txt', 'es module');
// 单独导入成员
import { writeFileSync } from 'fs';
writeFileSync('./foo.txt', 'es module');
ES Module与CommonJS交互:
ES Module中导入CommonJS中模块时,只能通过默认导入的当时导入模块,无法进行解构,import后的{}是固定语法,并非对象的结构。
// common.js
module.exports = {
hello: 'hello world'
}
// index.mjs
import common from './common.js';
console.log(common) // { hello: 'hello world' }
在CommonJS模块中导入ES Module在node中无法被原生支持。
ES Module与CommonJS的区别:
在CommonJS中,模块默认拥有内置的成员:require,module,exports,__filename,__dirname
在ES Module模块中,这些成员全都无法被支持,可以通过如下方式获取__filename与__dirname
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(import.meta.url)
通过babel进行ES Module的兼容:
安装babel: yarn add babel @babel/core @babel/preset-env
添加babel配置文件.babelrc
{
"presets": ["@babel/preset-env"]
}
编写代码通过yarn babel-node xxx.js或npx babel-node xxx.js运行脚本
// module.js
export const name = 'tom'
// index.js
import {name} from './module.js'
console.log(name)
yarn babel-node index.js,输出tom