> 技术文档 > 【前端】JS模块化解析-ESModule_es module

【前端】JS模块化解析-ESModule_es module


目录

  • 基本介绍
  • 基本使用
  • 导入导出方式3种
  • 结合使用
  • default用法
  • import函数
  • ES Module的解析流程
    • 导入的变量无法,导入方无法修改
    • 导出的变量,只能由导出方进行修改
    • 如果确实想在导入这边对导出的值进行修改,可以自定义set回调函数
  • CommonJS和ESModule相互引用
    • 实验一下

基本介绍

  • JavaScript没有模块化一直是它的痛点,所以才会产生我们前面学习的社区规范:CommonJSAMDCMD
    所以在ES推出自己的模块化系统时,大家也是兴奋异常。
  • ES Module和CommonJS的模块化有一些不同之处
  • 一方面它使用了import和export关键字:
  • 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式
  • ES Module模块采用export和import关键字来实现模块化
    • export负责将模块内的内容导出,
    • import负责从其他模块导入内容;
  • 了解:采用ES Module将自动采用严格模式:use strict

基本使用

// foo.jsexport const name = \"why\"export const age = \"18\"
// main.jsimport {name,age} from \"./foo.js\"console.log(name)console.log(age)
<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title></title></head><body> <script src=\"main.js\" type=\"module\"></script></body></html>

让模块化生效的两个前提条件

  1. script标签要加上 type=“module”
  2. index.html的访问必须是以服务器的方式访问。也就是访问协议必须是http或者https 直接本地文件打开file://这个协议是不支持的

导入导出方式3种

// foo.js// 导出方式// 1. 第一种方式: export 声明语句export const name = \"why\"export const age = \"18\"export function foo(){console.log(\"foo function\")}export class Person {}
// 2. 第二种: export 导出 和 声明分开const name = \"why\"const age = \"18\"function foo(){console.log(\"foo function\")}export {name,age,foo}
// 3. 第三种方式:第二种导出时起别名const name = \"why\"const age = \"18\"function foo(){console.log(\"foo function\")}export { name as fName, age as fAge, foo as fFoo}

导入方式

// 导入方式// 1.导入方式1:普通导入import {name,age,foo,Person} from \"./foo.js\"// 2.导入方式二:起别名import {name as fName,age as fAge,foo as fFoo} from \"./foo.js\"// 3.导入方式三:将所有哦的内容放到一个标识符中import * as foo from \"./foo.js\"console.log(foo.name)console.log(foo.age)console.log(name)console.log(age)

结合使用

当我们某个目录下有很多js文件,导出了很多工具方法如下

// utils/math.jsfunction add(a,b){return a+b;}function sub(a,b){return a-b}export {add,sub}
// utils/format.jsfunction timeFormat(){return \"2222-12-12\"}function priceFormat(){return \"222.22\"}export {timeFormat,priceFormat}

然后在main.js中引入这些模块的函数,代码会变的很麻烦如下

// main.jsimport {add,sub} from \"./utils/math.js\"import {timeFormat,priceFormat} from \'./format.js\'

因此一般在utils目录下会统一定义一个index.js用来将该目录下的所有需要导出的变量进行一个统一导出。如下
这样mian.js就可以只导入utils/index.js即可

// utils/index.js// 导出方式一import {add,sub} from \'./math.js\'import {timeFormat,priceFormat} from \'./format.js\'export {add,sub,timeFormat,priceFormat}

导出方式一还是显得太复杂,可以结合使用

// utils/index.js// 导出方式二:export {add,sub} from \'./math.js\'export {timeFormat,priceFormat} from \'./format.js\'// 如果要全部导出可如下写法// 导出方式三:export * from \'./math.js\'export * from \'./format.js\'

default用法

当写的某一个变量,是非常常用的时候,可以使用默认导出。默认导出的方式如下

// foo.jsconst name = \"zxh\"const age = 18 const foo = \'foo value\'// 1.默认导出的方式一:export {name,age,foo as default}// 2.默认导出的方式二:常见export default foo// 注意默认导出只能有一个

main.js在导入默认导出的变量时 如下使用

// 不用进行结构,可自定义接收的变量名 实际上是将 foo.js中的 foo赋值到aaa上import aaa from \'./foo.js\'console.log(aaa)

import函数

如下代码,在执行的时候是同步执行的,
首先浏览会先将要导入的文件下载下来,进行解析,然后才会执行后续的代码。

import {name,age,foo} from \'./foo.js\'console.log(\"导入的代码没有解析完之前,后续的代码是不会运行的\")

如果希望异步执行:比如页面初始化的时候并不需要用到导出的函数,只是在特定时候才需要使用。那么就可以使用import函数来进行异步执行

import(\"./foo.js\").then(res=>{console.log(\"res:\",res.name)})console.log(\"main.js 执行成功\")
// 查看控制台输出,可以看出是异步执行main.js 执行成功res:zxh

ES Module的解析流程

具体流程省略

导入的变量无法,导入方无法修改

需要重点提醒一下:导出的变量,是不允许在导入端被修改的
如下

// foo.jsexport let name = \"zifeiyu\"
import {name} from \"./foo.js\"// 这里修改是会报错的name = 333
// 控制台报错Uncaught TypeError: Assignment to constant variable. at main.js:3:6

导出的变量,只能由导出方进行修改

// foo.jsexport let name = \"zifeiyu\"setTimeout(() => {name = \"Kobe\"},100)
// main.jsimport {name} from \"./foo.js\"console.log(name)setTimeout(()=>{console.log(name)},2000)

可以看到控制台输出,说明导出方可以修改这个值

zifeiyuKobe

如果确实想在导入这边对导出的值进行修改,可以自定义set回调函数

// foo.jsexport let name = \"zxh\"export function setName(a){name = a}
import {name,setName} from \"./foo.js\"console.log(name)setName(3333333)console.log(name)

CommonJS和ESModule相互引用

解释一下:

  1. 模块A通过commonJS进行导出,然后模块B通过ESModule的方式进行导入。
  2. 模块C通过ESModule的方式进行导出,然后模块B通过commonJS的方式进行导入。

能否实现呢?
得分情况

  1. 浏览器:不能实现,因为默认不支持CommonJS
  2. node环境下:分情况
    1. 之前的node版本,只支持CommonJS
    2. 现在node版本逐渐在转用ESModule
  3. 正常开发环境webpack: 可以使用

实验一下

# 在一个干净的目录下# 1.通过npm帮我们初始化一下项目,也就是生成 package.jsonnpm init# 2.要使用webpack 需要安装 webpack 和 webpack-clinpm install webpack webpack-cli -D# 3.新建目录src,因为webpack默认的打包入库是 src/index.js

编写几个js进行互相引用
这边案例是打算

  • foo.js 通过commonjs导出
  • bar.js 通过ESmodule导出
  • index.js
    • 通过ESmodule 导入foo.js
    • 通过commonjs导入bar.js

代码如下

// src/foo.jsconst fooName = \"foo\"const fooAge = 18// commonjs导出module.exports = {fooName,fooAge}
// src/bar.jsconst barName = \"foo\"const barAge = 18// esmodule导出export{barName,barAge}
// src/index.jses moduleimport { fooName, fooAge } from \"./foo\"console.log(fooName, fooAge)// commonjs 导入const {barName,barAge} = require(\'./bar\')console.log(barName, barAge)
# 打包 会在当前命令所在目录下 创建一个dist文件夹 dist文件夹下会有一个main.jsnpx webpack 

在当前目录下创建index.js 对打包后的js进行引用即可

<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title></title></head><body><script src=\"dist/main.js\"></script></body></html>

浏览器访问可以看到正常输出,说明互相引用是可以的。

具体原理还得看main.js webpack打包出来的文件