前端工程化开发(三)—— ESModule

444 阅读6分钟

前端工程化开发(三)—— ESModule

认识 ESModule 模块化开发

  • 首先的话我们需要知道一点的是,我们的前端的在 ES6 之前是没有任何的模块化的规范的

  • CommonJS也只是社区中通过商讨,从而诞生的一种比较具备权威性的模块化开发模式,所以说我们的 ESModule 才是真真的模块化开发的规范

  • ESModule 和 CommonJS 的区别

    • 第一个不同的是使用的关键字的不同。esmodule 使用的是我们的 import 以及 export 关键字实现的模块化开发
    • 编译器加入了静态解析和动态引入的方式
  • ESModule 实现导入导出的方式

    • export 负责将我们的模块进行导出
    • import 负责将我们的模块进行导入
    • 同时我们的 esmodule 默认情况下使用的是我们的严格模式开展的以一种开发 use strict;
  • 首先我们使用原生的进行开发的时候,不使用任何的脚手架的时候

    • 我们使用 script 标签导入一个模块文件的时候需要进行增加属性 type="module"
    • 来表示我们的文件是以模块的形式导入的
    • 同时使用 module 形式实现的导入模块默认是在不同的作用域下的
    • 这样的话就实现了指定了我们的一个JS 文件就是一个模块了来进行使用了
    • <script src="./index.js" type="module"></script>
  • 模块内部的导出 export 规则

    • 导出的形式 : export {}
    • {} 内部存放的是我们的需要进行导出的一些模块内部的东西
    • 这种导出模式的话不是导出的对象,也不是字面量的增强
    • 就是单纯的需要暴露给外面的是什么,就用什么来进行导出即可
  • 导入规则 import 规则

    • import {} from "JS模块文件路径"
    • 但是这个时候会出现我们的协议问题,导致无法正常加载文件的,这个就是跨域 CORS 问题出现的场景一
// index.js
const name = "hello world"function get_value(param, ...args) {
    console.log(param)
}
​
export {
    get_value,
    name
}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ESModule</title>
</head>
<body>
<!--下面的一句话就是表示的是我们下面文件是一个模块的类型,这个时候据不会有什么作用域冲突的问题存在了-->
<!--这个时候的模块化开发就不用书写IIFE 来进行我们的自己产生作用域了-->
<script src="./index.js" type="module"></script>
</body>
</html>

export 关键字的使用

  • 导出方式一

    • export {
          get_value,
          name
      }
      
  • 导出方式二: 导出的时候可以给我们的标识符取别名

    • export {
          name as index_name,
          get_value as index_get_value
      }
      
  • 导出方式三: 在我们的标识符进行定义的时候直接导出

    • export function get_value {}
      export const name = "hello world"
      

import 导入方式

  • 导入方式一

    • import {name, age, get_value} from "./index.js" 
      
  • 导入方式二:在我们的外部变量进行导入的时候取别名防止变量名冲突

    • import {name as index_name, age, get_value} from "./index.js"
      
  • 导入方式三: 导入的时候就给我们的模块取别名

    • import * as index from "./index.js"console.log(index.name)
      index.get_value(index.name)
      

export 和 import 的结合使用

  • 解析流程是我们的先从静态资源服务器中下载 index.html 文件

  • 然后对我们的 HTML 文档进行一步一步的解析,遇到了 link 元素,就去下载 CSS 文件或者其他

  • 然后继续的加载 HTML 文档,遇到了 script 标签后,直接去下载 JS 文件

    • 但是在我们的 JS 文件使用了模块化的开发,实现导入了其他的 JS 文件,又进行下载
    • 这里的话我们浏览器加载 JS 文件含有三种模式,一种是默认的模式,defer 模式以及 async 模式
  • 在我们的实际的开发中,我们的每一个板块的话,我们是会进行编写一个主文件的

    • 该文件不用书写任何的业务逻辑,该部分只是实现的是将所有的工具进行集成
    • 然后统一暴露给外部进行使用的文件
    • 该文件的一半取名的话是: index.js
    • 在这个目录中实现我们的将所有导入的文件全部导出即可
    • 或者说使用我们的 export 和 import 的结合使用 export {} from "文件名路径"
  • 实现混合使用一

    • // index.js
      import {util01} from "./util01.js"
      import {util02} from "./util02.js"
      import {util03} from "./util03.js"
      import {util04} from "./util04.js"
      ​
      ​
      // 然后进行导出即可、
      export {
          util01,
          util02,
          util03,
          util04
      }
      
  • 实现混合使用二

    • // index.js
      export {util01} from "./util01.js"
      export {util02} from "./util02.js"
      export {util03} from "./util03.js"
      export {util04} from "./util04.js"
      
  • 实现混合使用三

    • // index.js
      export * from "./util01.js"
      export * from "./util02.js"
      export * from "./util03.js"
      export * from "./util04.js"
      

export default 导出用法

  • 这个就是我们的默认导出

    • 第一种写法

      • // utils/formatCount.js
        function formatCount(timeStr) {
            // JS 函数代码体    
        }
        export defdault formatCount
        
      •   import 别名 from "./utils/formatCount.js"
        
    • 第二种写法

      • // utils/formatCount.js
        export default function(timeStr) {
            // JS 函数代码体    
        }
        
      •   import 别名 from "./utils/formatCount.js"
        
  • 注意我们的一个模块只能有一个默认导出,不能有多个

import 函数

  • import 函数是一个异步的操作,利用在我们的逻辑处理中,返回的是一个 Promise 对象
  • import("./utils").then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err)
    })
    

import.meta

  • 该属性主要是用来实现保存的是我们当前模块的元数据,包含了我们当前模块 URL
  • console.log(import.meta)

ESModule 的解析流程

  • ESModule 的解析流程主要划分为了三步

    • 阶段一: 构建(Construction),根据地址查询每一个 JS 文件,并下载,将其解析为模块记录(Module Record

      • 查找:实现的就是从哪里进行下载包含模块的文件,这一步也是模块解析
      • 下载:获取 JS 文件
      • 解析:将文件解析为模块记录
    • 阶段二:实例化(Instantiation),对模块记录进行初始化,并且分配内存空间,解析模块的导入导出语句,把模块指向对应的内存地址

      • 实例化步骤就是写入内存,生成模块环境记录,但是这一步并没有对任何的变量进行赋值操作

      • 所以说在这一个过程中就可以解释为什么我们的 esmodule 模块化开发可以避免作用域问题了,就是因为其实现导出是

        • 该模块中指定导出的被添加到环境记录中的变量
    • 阶段三:运行(求值)(Evaluation),运行JS 代码,计算值,并且将其将值填充到内存地址中

      • 内存区中填充绑定数据具体的值
    • CSDN 博客讲解

总图解析

image-20241203002157968.png

构建过程图

image-20241203005551442.png

实例化以及求值阶段图

image-20241203005708416.png

总结

  • hacks 网站使用
  • 该部分主要讲解了关于我们的前端模块化开发中的 ESMdule
  • 同时我们也浅谈了一下关于导入导出的不同的表现形式以及 ESModule 的详细解析流程