课程名称: Vite 从入门到精通,玩转新时代前端构建法则
课程章节: 4-2 【知识拓展】ES Module详解
课程讲师: Jokcy
课程内容:
ES Module详解
讲解以下几个词:
ES Module是随着ES6语法推出的正式的JavaScript模块语法。在JavaScript的发展初期,因为脚本量往往都非常小,所以可能一个或者几个独立运行的JS脚本文件就足够一个网站使用了。而随着前端框架的流行,前端应用愈发复杂,单纯的独立运行JS文件显然已经跟不上应用规模的发展速度,在这个过程中,前端社区发展出了一些非标准的模块管理方案:
但是这些模块管理方法本质上都是通过JS脚本的执行来进行管理,并不是原生的Native模块管理能力。
而ES Module则是原生的JS语法,ES Module增加了import
和export
两个主要的关键字,并且使用特殊的语句来完成模块的功能:
import React, { useEffect } from 'react'
这是模块导入的语句,从react
中引入默认的对象,以及单独导入useEffect
函数。
export defualt SomeObject export const fun = () => {}
这是模块导出的语句,第一个作为默认导出,第二个则为具名导出。
因为这两个是关键字而不像require
是一个函数,所以他们的使用具有一定的限制,这两者都只能在模块的顶级作用域中使用:
// worked import xxx from 'xxx' export const a = 1 // not work if (x = 1){ import xxx from 'xxx' export const a = 1 }
语法:
一、import
import
是模块导入的关键字,在ES Module中模块引入具有两种主要的方式:
在上述的例子中,引入的React
就是默认导入的结果,其对应的是在react
模块中的export default
。
而useEffect
的引入就属于具名导入,其对应的则是在react
模块中进行export const useEffect
或者export { useEffect }
。
在具名导入时,我们还可以同时进行重命名,比如import { useEffect as myUseEffect } from 'react'
,就可以在引入时给变量进行一个新的命名。
注意在模块导入时还有一种不太常用的语法:
import * as React from 'react'
这里的*
对应的其实就是react
模块中的所有具名导出,比如上述的useEffect
在这里就可以通过React.useEffect
进行使用。需要注意的是这种方式引入的React
和import React from 'react'
中的React
是不一样的,举个例子:
export const a = 1 export const b = 2 export default { a }
那么这里通过import *
的方式引入的会具有a
和b
两个属性,而默认import
则只有a
。
二、export
export
的使用方式则相对更丰富一些:
export default xxx // 默认导出 export const name = 'Jokcy' // 具名导出 const age = 18 export { age } // 集合形式的具名导出 export { age as JokcyAge } // 导出并重命名 export * from 'another-module' // re- export 重新导出 export { someValue } from 'another-module' // 具名重新导出
我们可以简单地在导出语句中是否看到具体的变量名的方式来归纳其导出方式是否属于具名导出,事实上我们可以认为除了默认导出方式外其他的都属于具名导出。
在网页上使用ES Module
在现在主流的现代浏览器上基本都已经支持了ES Module的JS模块管理功能。我们可以通过:
<script type="module" src="/path/to/file.js"></script>
来加载并执行ES Module的JS脚本。需要注意的是这里的type="module"
是不能省略的,浏览的只会对加了这个属性的JS脚本采用ES Module的形式进行解析,不然可能会出现报错,比如在火狐浏览器中就会提示SyntaxError: import declarations may only appear at top level of a module
。
对于ES Module的JS脚本,在浏览器解析并执行之后,对文件内的import
语句,浏览器则会根据其路径继续请求其依赖的模块,也就是说只有真正被依赖的JS模块才会被真正的加载。
动态加载模块
那么如果我们需要在一些条件判断中来决定是否进行一些模块的加载,是否就做不到了呢?毕竟我们之前已经说过import
和export
必须在顶层作用域中使用。
如果是通过import
和export
关键字,那么确实是无法实现的。不过在ES Module的规范中,也为我们提供了动态导入模块的工具函数:import()
。
import()
函数接收一个字符串作为需要引入的模块路径,比如import('./my-route2.js')
就表示现在我需要加载my-route2
这个模块。该函数返回一个Promise
,当模块加载成功之后会调用then
,反之则进入catch
。在then
中我们会拿到对应模块的所有导出:
import('./a.js').then(m => { m.sayHello() })
因为其是一个函数,所以自然可以在任何地方被调用,不会受到语法的限制。
mjs 和 js
在ES Module宣布时,也同时宣布了mjs后缀名。统一的认知是以mjs结尾的文件被视为ES Module的文件,而js文件则为非ES Module文件。在nodejs 14之后已经正式支持了ES Module,如果你没有在package.json中声明module: true
,那么你只能在mjs结尾的文件中使用ES Module。
但业界并没有局限于该规范,毕竟后缀名更多仍然是便于阅读,主流的模块管理工具比如webpack、rollup他们都有自己独有的配置或者识别方式来判断某个文件是否是ES Module的模块,所以也并不需要局限于后缀名带来的限制。
和require
的区别
一个大部分人会关心的问题就是ES Module和我们用的最多的Common JS模块的区别。
Common JS模块通过require
函数和module
对象来进行模块管理,而这两个函数则是在JS脚本执行前,有JS引擎通过VM进行注入的全局对象,本质上他们就是JS的函数或者对象,可以在任意地方被引用和使用,他们的运行的同时也是JS运行的同时。
而ES Module的import
和export
则不同,他们是关键字,在脚本被语法解析还没有执行时,就已经可以知道该模块导入或者导出了什么内容,本质上这两个模块管理方式的执行时机就是不同的。
一个典型的区别就是import
导入的内容是静态的不允许修改的,而require
引入的对象本身就是原始对象的引用,可以直接进行修改。
课程收获:
理解除了 ES Module 之外,都是非标准的模块管理方案。
对 ES Module 的语法有了深入的了解。
明白对如何 动态加载模块,还有在网页上使用 ES Module。
明白 mjs 和 js 的区别,还有 ES Module 和 require 的区别。