模块化

什么是模块化

模块化开发的最终目的是将程序划分成一个个小的结构

  • 在这些结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他结构

  • 在这些结构中可以将自己希望暴露的变量、函数、对象导出给其他结构使用

  • 也可以通过某些方式导入其他结构的变量、函数、对象来使用

早期自定义模块化

早期js没有模块化概念和功能,容易造成命名冲突问题,只能借助函数作用域实现模块化

缺陷

  • 模块名不得重复,且需记忆返回对象名称
  1. /* moduleA.js */
  2. const moduleA=(function(){
  3. const name='rv'
  4. const age=20
  5. return{
  6. name,
  7. age
  8. }
  9. })()
  10. /* moduleB.js */
  11. const moduleB=(function(){
  12. const name='why'
  13. const age=18
  14. return{
  15. name,
  16. age
  17. }
  18. })()
  19. /* index.js */
  20. console.log(moduleA.age) // 20
  21. console.log(moduleB.age) // 18

CommonJS规范

CommonJS只是一个规范,最初提出是在浏览器以外的地方使用,并且当时命名为ServerJS,后为了体现他的广泛性改名为CommonJS,简称CJS

  • NodeCommonJS在服务器端的一个具有代表性的实现

  • BrowserifyCommonJS在浏览器中的一种实现

  • webpack模块化打包工具具备对CommonJS的支持和转换

CommonJS基本使用

  1. /* moduleA.js */
  2. const name='rv'
  3. const age=20
  4. module.exports{
  5. name,
  6. age
  7. }
  8. /* main.js */
  9. const {name,age}=require('./moduleA.js')
  10. console.log(age) // 20

require查找规则

情况一:内置模块('path','http')

情况二:相对路径('./···','../···')

第一步:如果有后缀名

则按照后缀名的故事查找对应位置的文件

第二步:若没有后缀名

  1. 按照确切的文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.node扩展名进行加载
  5. 加载失败,报错

第三步:没有找到对应的文件

则将其看做一个目录,查找目录下面的index文件

  1. 查找/index.js文件
  2. 查找/index.json文件
  3. 查找/index.node文件

第四步:报错not found

情况三:非相对路径的第三方模块('axios')

如果require()的模块标识符不是一个内置模块,也没有以./../开头

则尝试从当前文件夹的/node_modules文件夹中加载第三方模块,并不断返回该文件夹的父级文件夹的/node_modules文件夹中尝试查找该第三方模块直至根目录为止

最后报错not found

19.模块化 - 图1

模块加载过程

模块只在第一次引入时,会执行一次,并缓存提供多次引入

CMD规范

CMD规范也是应用于浏览器的一种模块化规范

  • CMD(Common Module Definition 通用模块定义)
  • 采用了异步加载模块,吸收了很多CommonJS优点
  • 目前很少使用

ES Module

ES Module与CommonJS的模块化不同之处

  • 一方面关键字不同(import,export)
  • 另一方面其采用编译期的静态分析,并且也加入了动态引用的方式
  • 采用ES Module将自动进入严格模式
  1. <!-- index.html -->
  2. <script src="./main.js" type="module">
  3. /* 以模块的形式引入JS文件,否则报错不得在模块外使用import */
  4. </script>
  1. /* main.js */
  2. import {name} from './foo.js'
  3. console.log(name)
  4. /* foo.js */
  5. export const name='rv'

导出方式

方式一:export声明语句

  1. export const name='rv'
  2. export function foo(){···}
  3. export class Person{···}

方式二:export与声明分开

  1. const name='rv'
  2. function foo(){···}
  3. class Person{···}
  4. export{
  5. name,
  6. foo,
  7. Person
  8. }

方式三:命名导出

  1. const name='rv'
  2. function foo(){···}
  3. class Person{···}
  4. export{
  5. name as myName,
  6. foo as myFoo,
  7. Person as MyPerson
  8. }

方式四:默认导出

默认导出仅能有一个,否则报错重复导出default

  1. const name='rv'
  2. function foo(){···}
  3. class Person{···}
  4. // 默认导出方式一
  5. export{
  6. name as default,
  7. foo,
  8. Person
  9. }
  10. // 默认导出方式二
  11. export default name

导入方式

方式一:普通导入

  1. import {name,foo,Person} from '···'

方式二:起别名

  1. import {name as myName,foo as myFoo,Person as MyPerson} from '···'

方式三:将导出的所有内容存入标识符中

  1. import * as myModule from '···'

方式四:默认导入

  1. import myModule from '···'

import()函数

import关键字引入是会堵塞代码运行的

import()则是异步引入,不会造成堵塞,其返回的结果是一个Promise对象

  1. import('./foo.js').then(res=>{
  2. console.log(res)
  3. })

import.meta

ES11新增特性

meta属性本身也是一个对象,存储着当前模块所在地址{url:'存储地址'}

  1. console.log(import.meta)

ES Module解析流程

阶段一 构建(Construction)

根据地址查找JS文件,并且下载,将其解析成模块记录(Module Record)

阶段二 实例化(Instantiation)

对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址

阶段三 运行(Evaluation)

运行代码,计算值,并将值填充进内存地址中