模块化
什么是模块化
模块化开发的最终目的是将程序划分成一个个小的结构
在这些结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他结构
在这些结构中可以将自己希望暴露的变量、函数、对象导出给其他结构使用
也可以通过某些方式导入其他结构的变量、函数、对象来使用
早期自定义模块化
早期js没有模块化概念和功能,容易造成命名冲突问题,只能借助函数作用域实现模块化
缺陷
- 模块名不得重复,且需记忆返回对象名称
/* moduleA.js */const moduleA=(function(){const name='rv'const age=20return{name,age}})()/* moduleB.js */const moduleB=(function(){const name='why'const age=18return{name,age}})()/* index.js */console.log(moduleA.age) // 20console.log(moduleB.age) // 18
CommonJS规范
CommonJS只是一个规范,最初提出是在浏览器以外的地方使用,并且当时命名为ServerJS,后为了体现他的广泛性改名为CommonJS,简称CJS
Node是CommonJS在服务器端的一个具有代表性的实现Browserify是CommonJS在浏览器中的一种实现webpack模块化打包工具具备对CommonJS的支持和转换
CommonJS基本使用
/* moduleA.js */const name='rv'const age=20module.exports{name,age}/* main.js */const {name,age}=require('./moduleA.js')console.log(age) // 20
require查找规则
情况一:内置模块('path','http')
情况二:相对路径('./···','../···')
第一步:如果有后缀名
则按照后缀名的故事查找对应位置的文件
第二步:若没有后缀名
- 按照确切的文件名进行加载
- 补全
.js扩展名进行加载 - 补全
.json扩展名进行加载 - 补全
.node扩展名进行加载 - 加载失败,报错
第三步:没有找到对应的文件
则将其看做一个目录,查找目录下面的index文件
- 查找
/index.js文件 - 查找
/index.json文件 - 查找
/index.node文件
第四步:报错not found
情况三:非相对路径的第三方模块('axios')
如果require()的模块标识符不是一个内置模块,也没有以./或../开头
则尝试从当前文件夹的/node_modules文件夹中加载第三方模块,并不断返回该文件夹的父级文件夹的/node_modules文件夹中尝试查找该第三方模块直至根目录为止
最后报错not found

模块加载过程
模块只在第一次引入时,会执行一次,并缓存提供多次引入
CMD规范
CMD规范也是应用于浏览器的一种模块化规范
- CMD(Common Module Definition 通用模块定义)
- 采用了异步加载模块,吸收了很多CommonJS优点
- 目前很少使用
ES Module
ES Module与CommonJS的模块化不同之处
- 一方面关键字不同(
import,export) - 另一方面其采用编译期的静态分析,并且也加入了动态引用的方式
- 采用ES Module将自动进入严格模式
<!-- index.html --><script src="./main.js" type="module">/* 以模块的形式引入JS文件,否则报错不得在模块外使用import */</script>
/* main.js */import {name} from './foo.js'console.log(name)/* foo.js */export const name='rv'
导出方式
方式一:export声明语句
export const name='rv'export function foo(){···}export class Person{···}
方式二:export与声明分开
const name='rv'function foo(){···}class Person{···}export{name,foo,Person}
方式三:命名导出
const name='rv'function foo(){···}class Person{···}export{name as myName,foo as myFoo,Person as MyPerson}
方式四:默认导出
默认导出仅能有一个,否则报错重复导出default
const name='rv'function foo(){···}class Person{···}// 默认导出方式一export{name as default,foo,Person}// 默认导出方式二export default name
导入方式
方式一:普通导入
import {name,foo,Person} from '···'
方式二:起别名
import {name as myName,foo as myFoo,Person as MyPerson} from '···'
方式三:将导出的所有内容存入标识符中
import * as myModule from '···'
方式四:默认导入
import myModule from '···'
import()函数
import关键字引入是会堵塞代码运行的
import()则是异步引入,不会造成堵塞,其返回的结果是一个Promise对象
import('./foo.js').then(res=>{console.log(res)})
import.meta
ES11新增特性
meta属性本身也是一个对象,存储着当前模块所在地址{url:'存储地址'}
console.log(import.meta)
ES Module解析流程
阶段一 构建(Construction)
根据地址查找JS文件,并且下载,将其解析成模块记录(Module Record)
阶段二 实例化(Instantiation)
对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址
阶段三 运行(Evaluation)
运行代码,计算值,并将值填充进内存地址中
