# 认识 ES Module
JavaScript 没有模块化一直是它的痛点,所以才会产生我们前面学习的社区规范:CommonJS、AMD、CMD 等,
所以在 ES 推出自己的模块化系统时,大家也是兴奋异常。
ES Module 和 CommonJS 的模块化有一些不同之处:
一方面它使用了 import 和 export 关键字;
另一方面它采用编译期的静态分析,并且也加入了动态引用的方式;
ES Module 模块采用 export 和 import 关键字来实现模块化:
export 负责将模块内的内容导出;
import 负责从其他模块导入内容;
了解:采用 ES Module 将自动采用严格模式:use strict
如果你不熟悉严格模式可以简单看一下 MDN 上的解析;
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
# 案例代码结构组件
< script src = " ./main.js" type = " module" > </ script>
如果直接在浏览器中运行代码,会报类似如下错误:
Access to script at file:// ...... from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
这个在 MDN 上面有给出解释:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules
你需要注意本地测试 — 如果你通过本地加载 Html 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为 Javascript 模块安全性需要。
你需要通过一个服务器来测试。
我这里使用的 VSCode,VSCode 中有一个插件:Live Server
# exports 关键字
export 关键字将一个模块中的变量、函数、类等导出;
我们希望将其他中内容全部导出,它可以有如下的方式:
方式一:在语句声明的前面直接加上 export 关键字
方式二:将所有需要导出的标识符,放到 export 后面的 {} 中
注意:这里的 {} 里面不是 ES6 的对象字面量的增强写法,{} 也不是表示一个对象的;
所以: export {name: name},是错误的写法;
方式三:导出时给标识符起一个别名
# export 声明语句
// 加上type="module"属性,告知加载的文件是个模块 < script src = " ./main.js" type = " module" > </ script>
export const name = "lain" export const age = 16
import { name, age } from "./foo.js" console. log ( name) console. log ( age)
# export 导出 和 声明分开
< script src = " ./main.js" type = " module" > </ script>
const name = "lain" const age = 16 function foo ( ) { console. log ( 'foo function' ) } export { name, age, foo }
注意: export后面这个大括号是固定语法,可不是对象
main.js
import { name, age, foo } from "./foo.js" console. log ( name) console. log ( age) console. log ( foo)
# 第二种导出时起别名
< script src = " ./main.js" type = " module" > </ script>
const name = "lain" const age = 16 function foo ( ) { console. log ( 'foo function' ) } export { name as fName, age as fAge, foo as fFoo }
注意: export后面这个大括号是固定语法,可不是对象
main.js
import { fName, fAge, fFoo } from "./foo.js" console. log ( fName) console. log ( fAge) console. log ( fFoo)
# import 关键字
import 关键字负责从另外一个模块中导入内容
导入内容的方式也有多种:
方式一:import {标识符列表} from ' 模块 ';
注意:这里的 {} 也不是一个对象,里面只是存放导入的标识符列表内容;
方式二:导入时给标识符起别名
方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上
# 普通导入
< script src = " ./main.js" type = " module" > </ script>
export const name = "lain" export const age = 16
import { name, age } from "./foo.js" console. log ( name) console. log ( age)
# 别名方式
< script src = " ./main.js" type = " module" > </ script>
export const name = "lain" export const age = 16
import { name as fName, age as fAge, foo as fFoo } from "./foo.js" console. log ( fName) console. log ( fAge) console. log ( fFoo)
# 将导出的所有内容放到一个标识符中
< script src = " ./main.js" type = " module" > </ script>
const name = "lain" const age = 16 function foo ( ) { console. log ( 'foo function' ) } export { name, age, foo }
import * as foo from "./foo.js" console. log ( foo) console. log ( foo. name) console. log ( foo. age) console. log ( foo. foo)
# 举个栗子 export 和 import 结合使用
补充:export 和 import 可以结合使用
export { sum as fooSum } from './foo.js'
为什么要这样做呢?
在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中;
这样方便指定统一的接口规范,也方便阅读;
这个时候,我们就可以使用 export 和 import 结合使用;
案例:这是一些工具函数,将它们全部导出实际运用
format.js
function timeFormat ( ) { return "2021-11-25" } function priceFormat ( ) { return "2021.11" } export { timeFormat, priceFormat }
function add ( num1, num2 ) { return num1 + num2 } function sub ( num1, num2 ) { return num1 - num2 } export { add, sub }
export function request ( ) { }
我一般会建一个 index.js
文件,将它们全部导入进来,最后统一导出,我认为这种方式阅读体验会比较好
# 导出方式一
import { add, sub } from './math.js' import { timeFormat, priceFormat } from './format.js' export { add, sub, timeFormat, priceFormat }
这种方式比较繁琐,不方便,下面将使用简单一些的导出方式
# 导出方式二
export { add, sub } from './math.js' export { timeFormat, priceFormat } from './format.js'
# 导出方式三
export * from './math.js' export * from './format.js'
# Default 用法
前面我们学习的导出功能都是有名字的导出(named exports):
在导出 export 时指定了名字;
在导入 import 时需要知道具体的名字;
还有一种导出叫做默认导出(default export)
默认导出 export 时可以不需要指定名字;
在导入时不需要使用 {},并且可以自己来指定名字;
它也方便我们和现有的 CommonJS 等规范相互操作;
注意:在一个模块中,只能有一个默认导出(default export);
foo.js
# 默认导出的方式一
# 默认导出的方式二
import foo from './foo.js' console. log ( foo)
# import 用法
# import 函数
const foo = "foo value" export { foo }
可以把import是当成一个函数
import函数返回的结果是一个Promise
const promise = import ( "./foo.js" ) . then ( res => { console. log ( "res:" , res. foo) } ) console. log ( promise)
import.meta 是一个给 JavaScript 模块暴露特定上下文的元数据属性的对象。
它包含了这个模块的信息,比如说这个模块的 URL;
在 ES11(ES2020)中新增的特性;
所以也可以把import当成一个对象,作为对象时还有一个属性:meta
ES11新增的特性:meta属性本身也是一个对象: { url: "当前模块所在的路径" }
# ES Module 的解析流程
ES Module 是如何被浏览器解析并且让模块之间可以相互引用的呢?
https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
ES Module 的解析过程可以划分为三个阶段:
阶段一:构建(Construction),根据地址查找 js 文件,并且下载,将其解析成模块记录(Module Record)
阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址。
阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中
# 不允许导入变量修改值
let name = "lain" let age = 16 export { name, age }
import { name, age } from './foo.js' console. log ( name, age) setTimeout ( ( ) => { name = "saber" age = 17 } , 1000 ) setTimeout ( ( ) => console. log ( name, age) , 2000 )
# 相互引用
npm install webpack webpack- cli
const name = "foo" module. exports = { name }
const name = "bar" const age = 100 export { name }
import { name } from './foo' console. log ( name) const bar = require ( "./bar" ) console. log ( bar. name)
( ( ) => { var e= { 825 : ( e, o, r ) => { "use strict" ; r. r ( o) , r. d ( o, { name : ( ) => t} ) ; const t= "bar" } , 717 : e => { e. exports= { name : "foo" } } } , o= { } ; function r ( t ) { var n= o[ t] ; if ( void 0 !== n) return n. exports; var a= o[ t] = { exports : { } } ; return e[ t] ( a, a. exports, r) , a. exports} r. n = e => { var o= e&& e. __esModule? ( ) => e. default : ( ) => e; return r. d ( o, { a : o} ) , o} , r. d = ( e, o ) => { for ( var t in o) r. o ( o, t) && ! r. o ( e, t) && Object. defineProperty ( e, t, { enumerable : ! 0 , get : o[ t] } ) } , r. o = ( e, o ) => Object . prototype. hasOwnProperty . call ( e, o) , r. r = e => { "undefined" != typeof Symbol&& Symbol. toStringTag&& Object. defineProperty ( e, Symbol. toStringTag, { value : "Module" } ) , Object. defineProperty ( e, "__esModule" , { value : ! 0 } ) } , ( ( ) => { "use strict" ; var e= r ( 717 ) ; console. log ( e. name) ; const o= r ( 825 ) ; console. log ( o. name) } ) ( ) } ) ( ) ;