1-4、JS 模块化详解
背景
JS 最开始的定位为:简单的页面设计 - 简单动画 + 基本的表格提交(1995 年网景耗时 2 周开发出来的)
并无模块化或命名空间的概念
后面前端发展越来越复杂,就对 JS 提出了“模块”的要求
模块化的阶段
幼年期:无模块化
通过多个 JS 文件来处理,写多个<script src="xxx"/>
来强行分隔
1 |
|
存在的问题:
- 污染全局作用域
- 因为都在同一个 HTML 里面引入,将污染全局作用域,存在变量名冲突等问题
- 不利于大型项目开发与多人团队共建
成长期:雏形-IIFE
IIFE:立即执行函数(语法侧优化),是模块化的基石
优势:作用域的封装,因为是函数所以有自己的作用域
1 |
|
使用 IIFE 实现一个简单的模块
1 |
|
成熟期:模块化爆发
CJS - CommonJS
来自于服务端(Nodejs)定义的模块化加载和导出规范(同步加载)
1 |
|
其中通过require
导入,通过exports
或module.exports
导出
其中的关系为:require
引入的是module.exports
导出的
正常情况下exports === module.exports // true
异常情况
1 |
|
优点:
- 同步加载,易于理解代码执行
缺点:
- 同步加载大量文件时,会阻塞
- 因为是同步加载,所以无法按需异步加载
- 没有语言层面的支持: CommonJS 不是 JavaScript 的语言层面的特性,而是一种规范。相比之下,ES6 模块是语言层面的特性,得到了更好的集成和支持。
AMD - Asynchronous Module Definition(异步模块定义)
针对浏览器端的模块加载规范,支持异步加载,不阻塞页面渲染
著名的框架为 require.js
1 |
|
优点:
- 异步加载
- 提高了模块化开发
缺点:
- 语法繁琐
- 需要显示声明依赖
UMD - Universal Module Definition(通用模块定义)
UMD 是兼容多种模式(CJS/AMD 等)的模块加载器
1 |
|
CMD - Common Module Definition(同步模块加载)
强调模块的加载与使用是同时的
著名框架 sea.js
1 |
|
优点:
- 对依赖的加载可控,能达到“按需加载”,依赖就近
缺点:
- 不支持异步加载
新时代:官方支持
ESM - ES6 module
ES6 提供的模块加载机制
1 |
|
- 导出: 使用
export
关键字导出模块的功能。 - 导入: 使用
import
关键字导入其他模块的功能。 - 命名空间导入: 使用
import * as aliasName from 'module';
来导入整个模块的命名空间。 - 默认导出: 使用
export default
来指定一个模块的默认导出,可以使用import moduleName from 'module';
进行导入。 - 动态导入: 使用
import()
来动态加载模块,返回一个 Promise
优点:
- 模块文件有自己的作用域,不会污染全局
- 默认使用严格模式
缺点:
- 兼容性,低版本浏览器不一定兼容
模块化的目的
- 隔离逻辑与作用域
- 扩展协同的方便度
最终形成万物皆模块,作为前端工程化的基石
Webpack 知识
作用
将应用中的各种资源(js/css/图片/字体等)打包为浏览器可以直接运行静态文件。
打包流程
- 入口文件(Entry):从配置的入口文件为起点,进行依赖分析
- 遇到不同的资源(js/css/图片/字体等),将通过不同的加载器(loader),进行转换处理
- 依赖图:根据依赖关系生成一张依赖图,确保各模块的加载顺序
- 代码块(Chunk):根据依赖图生成代码块
- 插件(Plugin):处理各种任务。如:代码压缩、文件拷贝等
- 输出(Output):根据配置生成到对应目录,生成的目录有:js 文件夹、css 文件夹、img 文件夹、index.html、sourceMap 等
打包模式
默认使用 UMD,可以配置 CJS、AMD 等
一些问题
图片资源,最终打包成的代码是如何引用的?
举例:Vue + Webpack 的项目,模板(template)内的图片引入
1 |
|
打包处理逻辑为:1、将该图片打包到输出目录;2、将解析该相对 src 路径为输出目录的图片绝对路径
Vite 知识
作用
基于 ES Module 的构建工具
打包流程
- 启动开发服务器: 当你运行 vite 命令时,Vite 会启动一个开发服务器(基于 Koa)。
- ES Module 编译: Vite 将项目中的源代码文件(包括 JavaScript、CSS、Vue 文件等)通过 ES Module 编译器进行处理。这个过程中,它不会将所有模块打包成一个或多个文件,而是保持模块的原始结构。
- 按需编译: Vite 采用按需编译的策略,只有在用户请求时才会编译和提供相应的模块。这意味着不需要预先编译整个应用,大大提高了启动速度。
- 服务端渲染(如果需要): 对于 Vue 项目,Vite 可以支持服务端渲染(SSR),此时会执行服务端渲染的相关逻辑。
- 构建: 当需要生成生产环境的构建时,Vite 会使用 Rollup(一个 JavaScript 模块打包器)进行构建。这个构建过程会将模块打包成传统的、优化过的 JavaScript 文件,以适应生产环境的需要。
- 输出: 构建完成后,生成的文件将被输出到指定的目录,可以被部署到服务器上供浏览器加载
打包模式
Build 出来的产物为 ES Module
Webpack 与 Vite 的优缺点
|
| Webpack | Vite |
| — | — | — |
| 冷启动 | 慢 | 快(基于 ES Module) |
| 热更新 | 慢 | 快(基于 ES Module) |
| 生态 | 完善 | 较新 |
| 其他 | 配置复杂 | 配置简单 |
其他知识
严格模式
作用:使 JS 更安全,减少不确定性
使用:’use strict’
特点:
- 禁止使用未声明的变量: 在严格模式下,如果使用未声明的变量,将抛出 ReferenceError。
- this 的值为 undefined: 在严格模式下,如果函数不是作为对象的方法调用,this 的值将为 undefined。
调用栈
使用new Error().stack
可以获取
1 |
|
Rollup
一个 JS 模块打包器,产物为 ESModule。相比于 Webpack 更小巧高效
组件库搭建所需
面试题
script 标签的参数:async、defer
都是用于控制脚本执行时机
- 加载行为:
- async 和 defer 都不会阻塞页面渲染,允许页面继续加载。
- 执行时机:
- async:脚本加载完成后立即执行,与页面加载和其他脚本执行顺序无关。
- defer:按照它们在页面上出现的顺序执行,但会在文档解析完成后、DOMContentLoaded 事件触发前执行。
- 依赖关系:
- async:适用于相互独立、无依赖关系的脚本。
- defer:适用于有顺序依赖关系的脚本。
JQuery 源码-依赖处理
IFEE + 传参调配
1 |
|
一行代码如何兼容 AMD、CJS?
AMD 关键:define
CJS 关键:module.exports
1 |
|