Appearance
JavaScript 核心概念
导航目录
- 静态作用域和动态作用域
- 执行上下文与变量对象
- 作用域链
- 词法作用域和原型链
- 函数参数传递
- 手写核心方法
- AJAX vs Fetch
- let 底层实现
- 垃圾回收机制
- 函数式编程核心
- 发布订阅 vs 观察者模式
- 精度丢失问题
- WeakMap vs Map
- 小程序双线程架构
- 重绘 vs 回流
- setTimeout vs requestAnimationFrame
- script 加载策略
- 内存泄漏场景
- Vite 为何启动快
- Vue vs React 对比
- 前端打包必要性
- 开发环境标识
- 循环方法对比
- 数据类型细节
- 递减操作符
静态作用域和动态作用域
JavaScript 采用词法作用域(静态作用域),函数作用域在定义时就确定。而动态作用域中,函数作用域在调用时才确定。
对比说明
| 特性 | 词法作用域(静态) | 动态作用域 |
|---|---|---|
| 作用域确定时机 | 函数定义时 | 函数调用时 |
| JavaScript 支持 | ✓ 支持 | ✗ 不支持 |
| 典型语言 | JavaScript、Python | Bash、Emacs Lisp |
执行上下文与变量对象
当执行可执行代码时,会创建对应的执行上下文,包含三个核心属性:
- 变量对象(VO)
- 作用域链
- this
函数上下文
函数上下文中使用活动对象(AO) 表示变量对象。AO 在进入函数上下文时创建,通过arguments属性初始化。
AO 变化过程
- 进入执行上下文:添加形参、函数声明、变量声明
- 代码执行阶段:修改变量值
示例代码
js
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
// 进入执行上下文时AO:
AO = {
arguments: {0: 1, length: 1},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
// 代码执行后AO:
AO = {
arguments: {0: 1, length: 1},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}作用域链
查找变量时从当前上下文的变量对象开始,沿父级执行上下文逐级查找。作用域链是由多个执行上下文的变量对象构成的链表。
示例代码
js
function foo() {
function bar() {
// bar作用域链:[bar.AO, foo.AO, global.VO]
}
}词法作用域和原型链
词法作用域
词法作用域:解决的是 "变量从哪里来?" 的问题,即变量的查找规则。
JavaScript 引擎通过作用域链来查找变量。作用域链是由当前执行上下文和所有外层执行上下文的变量对象组成。查找变量时,会从当前作用域开始,逐级向上(向外)查找,直到全局作用域。如果找不到,就会抛出 ReferenceError。
原型链
原型链:解决的是 "属性或方法从哪里来?" 的问题,即对象属性的查找规则。
原型链是 JavaScript 实现继承的主要机制。每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 或 Object.getPrototypeOf() 访问),它指向另一个对象(它的原型)。当试图访问一个对象的属性时,如果该对象自身没有这个属性,就会在其原型对象上查找,依此类推,直到找到属性或到达链的末端(null)。
对比说明
| 特性 | 词法作用域 | 原型链 |
|---|---|---|
| 查找目标 | 变量 | 对象属性/方法 |
| 查找方向 | 从内向外 | 从对象到原型 |
| 查找失败 | 抛出 ReferenceError | 返回 undefined |
| 实现机制 | 作用域链 | 原型链 |
函数参数传递
ECMAScript 中所有函数参数都是按值传递。
传递方式
- 基本类型:传递值的副本
- 引用类型:传递引用的副本(共享传递)
示例代码
js
// 基本类型传递
var num = 1;
function changeNum(n) {
n = 2; // 修改副本,不影响原值
}
changeNum(num);
console.log(num); // 1
// 引用类型传递
var obj = { value: 1 };
function foo(o) {
o = 2; // 修改引用副本,不影响原对象
}
foo(obj);
console.log(obj.value); // 1(原对象未改变)
// 引用类型修改属性
var obj2 = { value: 1 };
function modifyObj(o) {
o.value = 2; // 通过引用修改对象属性
}
modifyObj(obj2);
console.log(obj2.value); // 2(原对象属性被修改)手写核心方法
实现 call
js
Function.prototype.call2 = function (context, ...args) {
// 处理 null/undefined 的情况
if (context == null) {
context = globalThis;
} else {
// 处理基本类型,需要转换为对象
context = Object(context);
}
// 使用 Symbol 避免属性名冲突
const fn = Symbol("fn");
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};实现 bind
js
Function.prototype.myBind = function (context, ...bindArgs) {
const self = this;
// 定义绑定函数
function boundFn(...callArgs) {
// 判断是否通过 new 调用
const isNewCall = this instanceof boundFn;
// new 调用时 this 指向新创建的对象,否则指向绑定的 context
const finalContext = isNewCall ? this : context;
// 合并绑定的参数和调用时的参数
const args = [...bindArgs, ...callArgs];
return self.apply(finalContext, args);
}
// 保持原型链,使绑定函数可以 instanceof 原函数
if (this.prototype) {
boundFn.prototype = Object.create(this.prototype);
}
return boundFn;
};实现 new
js
function myNew(Fn, ...args) {
// 1. 创建新对象,原型指向 Fn.prototype
const obj = Object.create(Fn.prototype);
// 2. 执行构造函数,绑定 this
const result = Fn.apply(obj, args);
// 3. 返回:如果构造函数返回对象则返回该对象,否则返回新创建的对象
return result !== null &&
(typeof result === "object" || typeof result === "function")
? result
: obj;
}AJAX vs Fetch
| 特性 | AJAX (XMLHttpRequest) | Fetch |
|---|---|---|
| 设计 | 回调函数 | Promise |
| API 设计 | 集中式 | 模块化(Request/Response/Headers) |
| 数据流 | 不支持分块读取 | 支持 Stream 分块读取 |
| CORS | 需手动处理 | 默认不发送 cookies |
| 错误处理 | 需检查 status | 需检查 response.ok |
| 浏览器支持 | 所有浏览器 | 现代浏览器(需 polyfill) |
示例代码
js
// AJAX 示例
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = function () {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();
// Fetch 示例
fetch("/api/data")
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));let 底层实现
let 的底层实现涉及词法环境和作用域链的管理:
- 编译阶段:扫描函数体,为 let 变量生成初始词法环境
- 执行上下文:进入块级作用域创建新词法环境
- 绑定变量值:运行时在词法环境中搜索变量
- 块级作用域:创建子遮蔽环境实现作用域隔离
示例代码
js
// let 的暂时性死区示例
{
// TDZ 开始
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
// TDZ 结束
console.log(x); // 10
}
// 块级作用域示例
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}垃圾回收机制
JavaScript 使用自动垃圾回收机制来管理内存,主要有以下几种算法:
引用计数
- 优点:立即回收垃圾
- 缺点:循环引用问题,计数器占用空间
标记清除
- 标记阶段:从根对象开始,标记所有可达对象
- 清除阶段:清除未被标记的对象
- 优点:实现简单,能处理循环引用
- 缺点:内存碎片
分代回收
- 新生代:新创建对象,回收频率高
- 老生代:长期存活对象,回收频率低
示例代码
js
// 循环引用问题(引用计数无法处理)
function createCycle() {
const objA = {};
const objB = {};
objA.ref = objB;
objB.ref = objA;
return { objA, objB };
}
// 标记清除可以处理循环引用
const cycle = createCycle();
cycle = null; // 两个对象都会被回收函数式编程核心
函数式编程是一种编程范式,强调使用纯函数、不可变性和函数组合。
1. 纯函数
纯函数:相同输入 => 相同输出,无副作用
js
function add(a, b) {
return a + b;
}2. 不可变性
不可变性:数据创建后不可修改
js
const newArr = [...arr, newItem];
const newObj = { ...oldObj, newProp: value };3. 高阶函数
高阶函数:函数作为参数或返回值
js
function multiplier(factor) {
return (num) => num * factor;
}
const double = multiplier(2);
console.log(double(5)); // 104. 函数组合
函数组合:将多个函数组合成一个函数
js
const compose =
(...fns) =>
(x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
const process = compose(
(x) => x * 2,
(x) => x + 1,
(x) => x - 3
);
console.log(process(5)); // (5 - 3) * 2 + 1 = 5发布订阅 vs 观察者模式
| 特性 | 发布订阅 | 观察者 |
|---|---|---|
| 耦合度 | 完全解耦 | 相互耦合 |
| 通信方式 | 消息通道 | 直接调用 |
| 扩展性 | 高(多发布/订阅者) | 低 |
| 中间件 | 需要事件中心 | 不需要 |
示例代码
js
// 发布订阅模式
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach((callback) => callback(data));
}
}
}
// 观察者模式
class Subject {
constructor() {
this.observers = [];
}
attach(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach((observer) => observer.update(data));
}
}
class Observer {
update(data) {
console.log("Received:", data);
}
}精度丢失问题
JavaScript 使用 IEEE 754 双精度浮点数表示数值,这会导致一些精度问题。
问题示例
js
0.1 + 0.2 !== 0.3; // true
0.1 + 0.2; // 0.30000000000000004
9007199254740991 + 1; // 精度丢失原因分析
浮点数二进制表示存在近似值,某些十进制小数无法精确表示为二进制浮点数。
解决方案
js
// 方法1:使用 toFixed
function add(a, b) {
return parseFloat((a + b).toFixed(10));
}
// 方法2:转换为整数计算
function add2(a, b) {
const multiplier = Math.pow(10, 10);
return (a * multiplier + b * multiplier) / multiplier;
}
// 方法3:使用第三方库(如 decimal.js)WeakMap vs Map
| 特性 | WeakMap | Map |
|---|---|---|
| Key 类型 | 仅对象 | 任意值 |
| 引用类型 | 弱引用 | 强引用 |
| 垃圾回收 | 自动回收 | 不回收 |
| 可枚举 | 否 | 是 |
| size 属性 | 无 | 有 |
| 遍历方法 | 无 | forEach、keys、values、entries |
示例代码
js
// Map 示例
const map = new Map();
const key1 = { name: "key1" };
const key2 = "key2";
map.set(key1, "value1");
map.set(key2, "value2");
console.log(map.size); // 2
console.log(map.get(key1)); // 'value1'
// WeakMap 示例
const weakMap = new WeakMap();
const obj = { id: 1 };
weakMap.set(obj, "metadata");
console.log(weakMap.get(obj)); // 'metadata'
// 当 obj 被垃圾回收时,WeakMap 中的条目也会被自动移除
obj = null;小程序双线程架构
小程序采用双线程架构来提高性能和用户体验。
架构说明
- 渲染层:WebView 渲染界面
- 逻辑层:JsCore 线程运行 JS 脚本
优势
避免 JS 执行阻塞渲染,提升用户体验。
通信机制
两个线程之间通过消息队列进行通信,数据传输需要序列化。
示例代码
js
// 逻辑层
Page({
data: {
message: "Hello",
},
onLoad() {
// 修改数据,触发渲染层更新
this.setData({
message: "Hello World",
});
},
});
// 渲染层
<view>{{ message }}</view>;重绘 vs 回流
| 特性 | 重绘 | 回流 |
|---|---|---|
| 触发条件 | 外观变化 | 布局变化 |
| 性能影响 | 较小 | 较大 |
| 优化建议 | 避免频繁样式修改 | 批量 DOM 操作 |
示例代码
js
// 触发重绘
element.style.color = "red";
element.style.backgroundColor = "blue";
// 触发回流
element.style.width = "100px";
element.style.height = "100px";
// 优化:批量操作,减少回流
const element = document.getElementById("my-element");
element.style.cssText = "width: 100px; height: 100px; color: red;";
// 优化:使用文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement("div");
fragment.appendChild(div);
}
document.body.appendChild(fragment);setTimeout vs requestAnimationFrame
| 特性 | setTimeout | requestAnimationFrame |
|---|---|---|
| 执行时机 | 指定时间后 | 下一帧开始前 |
| 主线程 | 阻塞 | 不阻塞 |
| 后台运行 | 继续执行 | 暂停 |
| 适用场景 | 通用定时 | 动画优化 |
示例代码
js
// setTimeout 示例
setTimeout(() => {
console.log("Delayed execution");
}, 1000);
// requestAnimationFrame 示例
function animate() {
// 更新动画状态
updateAnimation();
// 请求下一帧
requestAnimationFrame(animate);
}
// 开始动画
requestAnimationFrame(animate);
// 动画优化示例
let animationId;
function startAnimation() {
function loop() {
update();
draw();
animationId = requestAnimationFrame(loop);
}
animationId = requestAnimationFrame(loop);
}
function stopAnimation() {
cancelAnimationFrame(animationId);
}script 加载策略
| 特性 | 正常加载 | async | defer |
|---|---|---|---|
| HTML 解析 | 暂停 | 并行 | 并行 |
| 执行顺序 | 顺序执行 | 下载完立即执行 | HTML 解析完后顺序执行 |
| 使用建议 | - | 独立脚本 | 依赖 DOM 的脚本 |
示例代码
html
<!-- 正常加载:阻塞 HTML 解析 -->
<script src="script1.js"></script>
<!-- async:并行下载,下载完立即执行 -->
<script async src="script2.js"></script>
<!-- defer:并行下载,HTML 解析完后按顺序执行 -->
<script defer src="script3.js"></script>
<!-- 最佳实践:关键脚本使用 defer,非关键脚本使用 async -->
<script defer src="main.js"></script>
<script async src="analytics.js"></script>加载策略选择
- 正常加载:脚本执行顺序很重要,且脚本依赖 DOM
- async:独立脚本,不依赖其他脚本和 DOM
- defer:脚本执行顺序重要,但可以并行下载
内存泄漏场景
1. 意意全局变量
js
function leak() {
temp = "leak"; // 意外创建全局变量
}
// 解决方案
function noLeak() {
let temp = "no leak"; // 使用 let/const
}2. 未清除定时器
js
// 问题代码
setInterval(() => {
console.log("tick");
}, 1000);
// 解决方案
const timer = setInterval(() => {
console.log("tick");
}, 1000);
// 清除定时器
clearInterval(timer);3. DOM 引用未释放
js
// 问题代码
const element = document.getElementById("my-element");
// 未在不需要时解除引用
// 解决方案
function cleanup() {
const element = document.getElementById("my-element");
// 使用元素
element.innerHTML = "content";
// 清除引用
element = null;
}4. 闭包滥用
js
// 问题代码
function outer() {
const bigData = new Array(1000000);
return function inner() {
console.log(bigData.length);
};
}
// 解决方案
function outer() {
const bigData = new Array(1000000);
const result = bigData.length;
return function inner() {
console.log(result); // 只保留需要的数据
};
}Vite 为何启动快
Vite 的快速启动主要得益于以下几个设计:
核心优势
- 开发环境:直接使用 ES6 Module,无需打包
- 按需编译:仅编译当前请求的模块
- Esbuild 预构建:极速的依赖预构建
详细说明
ES6 Module 原生支持
- 开发环境直接使用浏览器的 ES Module 支持
- 避免了传统打包工具的完整打包过程
按需编译
- 只编译浏览器请求的文件
- 大大减少了启动时的编译时间
Esbuild 预构建
- 使用 Go 语言编写的 Esbuild 进行依赖预构建
- 比 Webpack 快 10-100 倍
示例对比
js
// 传统打包工具(如 Webpack)
// 需要打包整个项目,启动慢
npm run dev // 可能需要 10-30 秒
// Vite
// 直接使用 ES Module,启动快
npm run dev // 通常 1-3 秒Vue vs React 对比
| 特性 | Vue | React |
|---|---|---|
| 模板系统 | 基于 HTML 的模板 | JSX |
| 更新粒度 | 组件级 | 从根节点调度 |
| 响应式 | Proxy 拦截 | setState 触发 |
| 渲染方式 | 递归 | 可中断循环 |
| 学习曲线 | 较平缓 | 较陡峭 |
| 生态系统 | 官方主导 | 社区主导 |
示例代码
vue
<!-- Vue 示例 -->
<template>
<div>
<h1>{{ message }}</h1>
<button @click="updateMessage">Update</button>
</div>
</template>
<script>
export default {
data() {
return {
message: "Hello Vue",
};
},
methods: {
updateMessage() {
this.message = "Updated!";
},
},
};
</script>jsx
// React 示例
import React, { useState } from 'react';
function App() {
const [message, setMessage] = useState('Hello React');
const updateMessage = () => {
setMessage('Updated!');
};
return (
<div>
<h1>{message}</h1>
<button onClick={updateMessage}>Update</button>
</div>
);
}
```
<a id="nav-21"></a>
## 前端打包必要性
前端打包是现代前端开发的重要环节,主要解决以下问题:
### 1. 性能优化
- **Tree-Shaking**:移除未使用的代码
- **代码压缩**:减小文件体积
- **代码合并**:减少 HTTP 请求
- **按需加载**:提升首屏加载速度
### 2. 语法转换
- **TypeScript**:转换为 JavaScript
- **ES6+**:转换为 ES5 以支持旧浏览器
- **SCSS/Less**:转换为 CSS
### 3. 工程能力
- **Lint**:代码质量检查
- **测试**:自动化测试
- **CI/CD**:持续集成和部署
### 示例代码
```js
// 源代码(ES6+)
import { add } from './utils.js';
const result = add(1, 2);
console.log(result);
// 打包后(ES5)
'use strict';
var _utils = require('./utils.js');
var result = (0, _utils.add)(1, 2);
console.log(result);
```
<a id="nav-22"></a>
## 开发环境标识
开发过程中通常会使用不同的环境标识来区分不同的部署阶段。
| 环境 | 全称 | 用途 |
| --- | --- | --- |
| **DEV** | Development | 开发 |
| **SIT** | System Integration Test | 系统整合测试 |
| **UAT** | User Acceptance Test | 用户验收测试 |
| **PROD** | Production | 生产环境 |
### 示例代码
```js
// 环境配置
const ENV = process.env.NODE_ENV || 'development';
const config = {
development: {
apiUrl: 'http://localhost:3000',
debug: true
},
sit: {
apiUrl: 'https://sit.example.com',
debug: true
},
uat: {
apiUrl: 'https://uat.example.com',
debug: false
},
production: {
apiUrl: 'https://api.example.com',
debug: false
}
};
const currentConfig = config[ENV];
console.log('API URL:', currentConfig.apiUrl);
```
<a id="nav-23"></a>
## 循环方法对比
JavaScript 中有多种循环方法,各有不同的特点和适用场景。
| 方法 | 可中断 | 遍历内容 | 适用对象 |
| --- | --- | --- | --- |
| `forEach` | ❌ | 值 | Array |
| `for...in` | ✅ | 键 | Object/Array |
| `for...of` | ✅ | 值 | Array/Map/Set |
### 示例代码
```js
// forEach - 不可中断
const arr = [1, 2, 3, 4, 5];
arr.forEach((item, index) => {
console.log(item, index);
// 无法中断循环
});
// for...in - 遍历键
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key, obj[key]);
if (key === 'b') break; // 可以中断
}
// for...of - 遍历值
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value);
if (value === 2) break; // 可以中断
}
// 传统 for 循环
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
if (arr[i] === 3) break; // 可以中断
}
```
<a id="nav-24"></a>
## 数据类型细节
JavaScript 中有两个表示"空"的值,它们有不同的含义和用途。
### undefined
`undefined`:变量声明但未初始化
```js
let a;
console.log(a); // undefined
function foo(b) {
console.log(b); // undefined
}
foo();
const obj = {};
console.log(obj.property); // undefined
```
### null
`null`:空对象指针,表示"无值"或"空值"
```js
let a = null;
console.log(a); // null
function getNull() {
return null;
}
console.log(getNull()); // null
```
### 区别
| 特性 | undefined | null |
| --- | --- | --- |
| **类型** | undefined | object |
| **含义** | 未定义 | 空值 |
| **转换** | NaN | 0 |
| **typeof** | "undefined" | "object" |
### 最佳实践
```js
// 检查 undefined
if (typeof variable === 'undefined') {
// 变量未定义
}
// 检查 null
if (variable === null) {
// 变量为 null
}
// 检查 null 或 undefined
if (variable == null) {
// 变量为 null 或 undefined
}
```
<a id="nav-25"></a>
## 递减操作符
JavaScript 提供了前缀和后缀递减操作符,它们的执行顺序不同。
### 操作符说明
- `--a`:前缀递减,先递减后计算
- `a--`:后缀递减,先计算后递减
### 示例代码
```js
let a = 5;
let b = a-- + 2; // b=7, a=4(先计算后递减)
console.log(a, b); // 4, 7
let c = 5;
let d = --c + 2; // c=4, d=6(先递减后计算)
console.log(c, d); // 4, 6
// 更复杂的示例
let x = 10;
let y = x-- + --x;
console.log(x, y); // 8, 18
// 解释:
// x-- 使用当前值 10,然后 x 变为 9
// --x 先递减 x 从 9 到 8,然后使用 8
// y = 10 + 8 = 18
// 最终 x = 8
```
### 注意事项
- 递减操作符会修改原变量的值
- 在复杂表达式中使用时要注意执行顺序
- 建议在简单场景中使用,避免在复杂表达式中使用
```
## 检查对象属性是否存在的 4 种方式
### 1. in 运算符:`key in obj`
**特点**:检测自身+原型链上的key,存在返回true(含继承属性)。
```js
const obj = { a: 1 };
console.log('a' in obj); // true
console.log('toString' in obj); // true(继承自原型)
```
### 2. `obj.hasOwnProperty(key)`
**特点**:仅检测对象自身的key(排除原型链),最常用且精准。
```js
const obj = { a: 1 };
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('toString')); // false(原型链上的属性)
```
### 3. `obj[key] !== undefined`
**特点**:简单直接,但无法区分key存在但值为undefined的情况。
```js
const obj = { a: undefined };
console.log(obj['a'] !== undefined); // false(虽然属性存在,但值为undefined)
console.log(obj['b'] !== undefined); // false(属性不存在)
```
### 4. `Reflect.has(obj, key)`
**特点**:ES6新增,行为与in一致(检测自身+原型链),更符合函数式编程风格,可安全处理特殊对象(如冻结对象)。
```js
const obj = { a: 1 };
console.log(Reflect.has(obj, 'a')); // true
console.log(Reflect.has(obj, 'toString')); // true
const frozen = Object.freeze({ a: 1 });
console.log(Reflect.has(frozen, 'a')); // true(可以安全处理冻结对象)
```
### 对比总结
| 方法 | 检测范围 | 能否区分undefined | 函数式 | 推荐度 |
| --- | --- | --- | --- | --- |
| `in` | 自身+原型链 | ❌ | ❌ | ⭐⭐⭐ |
| `hasOwnProperty` | 仅自身 | ✅ | ❌ | ⭐⭐⭐⭐⭐ |
| `obj[key] !== undefined` | 自身 | ❌ | ❌ | ⭐⭐ |
| `Reflect.has` | 自身+原型链 | ❌ | ✅ | ⭐⭐⭐⭐ |
## 什么是模块化?
模块化是一种**将复杂系统拆分为独立、可复用的小单元(模块)** 的开发范式。每个模块聚焦单一功能,通过清晰定义的接口与其他模块通信,内部实现细节则被封装隐藏。
### 核心价值
1. **解耦性**:将代码按功能拆分(如请求库、工具函数、UI组件),避免牵一发而动全身
2. **复用性**:比如封装的防抖函数、表单组件,可在多个页面甚至多个项目中复用,减少重复开发
3. **可维护性**:模块职责单一,问题定位更精准(如网络错误只需检查API模块)
### 示例代码
```js
// math.js - 数学工具模块
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
// utils.js - 工具函数模块
export const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
// main.js - 主模块
import { add, multiply } from './math.js';
import { debounce } from './utils.js';
console.log(add(1, 2)); // 3
console.log(multiply(3, 4)); // 12
const debouncedFn = debounce(() => {
console.log('Debounced!');
}, 300);
```
### 模块化规范
- **CommonJS**:Node.js 使用,`require`/`module.exports`
- **ES Modules**:现代标准,`import`/`export`
- **AMD**:异步模块定义,RequireJS 使用
## 什么是工程化?
"工程化是**用系统化、规范化、工具化的方式管理前端全生命周期**,核心是解决**多人协作、大型项目、高效交付**的问题,把零散的开发流程变成可落地的标准流程。
### 前端工程化的核心落地环节
1. **开发规范**:ESLint/Prettier保障代码风格统一,Git提交规范记录变更语义
2. **构建优化**:利用Webpack/Vite实现代码压缩、Tree-Shaking、按需加载
3. **自动化流程**:CI/CD流水线自动运行测试、部署发布,减少人工干预
4. **架构设计**:建设私有组件库、搭建BFF层、落地微前端方案等
### 模块化 vs 工程化
- **模块化**:解决的是「**代码层面的混乱**」,如代码组织、依赖管理、复用性
- **工程化**:解决的是「**项目层面的混乱**」,如开发、构建、部署全流程的不规范
### 示例代码
```js
// .eslintrc.js - ESLint 配置
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:prettier/recommended'
],
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'prefer-const': 'error'
}
};
// .prettierrc - Prettier 配置
module.exports = {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 80
};
// package.json - 脚本配置
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "jest",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,css,md}",
"type-check": "tsc --noEmit"
}
}
```
### 工程化工具链
- **代码规范**:ESLint、Prettier、Stylelint
- **构建工具**:Webpack、Vite、Rollup、Parcel
- **测试框架**:Jest、Mocha、Cypress、Playwright
- **CI/CD**:GitHub Actions、GitLab CI、Jenkins
- **包管理**:npm、yarn、pnpm