跳到主要内容
HTML + CSS + JavaScript 进阶:性能优化、代码质量与工程化 | 极客日志
JavaScript Node.js 大前端
HTML + CSS + JavaScript 进阶:性能优化、代码质量与工程化 介绍前端进阶知识,涵盖性能优化、代码质量提升及工程化实践。性能方面包括加载流程、关键指标(LCP、CLS 等)、资源优化(CDN、压缩、懒加载)及缓存策略。代码质量涉及模块化编程、设计模式(单例、观察者等)及规范命名注释。工程化部分讲解包管理器(npm/yarn)、Git 版本控制、构建工具(Webpack/Vite)及调试技巧。旨在帮助开发者从“会写代码”迈向“专业开发”。
监控大屏 发布于 2026/3/29 更新于 2026/6/9 29 浏览前言
基础系列教你'怎么写代码',进阶系列教你'如何写好代码'。
如果用烹饪来比喻:
基础系列:教你认识食材,掌握基本的刀工和火候
进阶系列:教你食材搭配、营养搭配、摆盘艺术、厨房管理
这篇进阶文章,我们将聚焦三个核心方向:
性能优化 :让页面加载更快、运行更流畅
代码质量 :让代码更易维护、更健壮
工程化实践 :用现代工具提升开发效率
第一部分:前端性能优化
性能优化是前端工程师的核心竞争力。一个页面,功能再强大,如果加载慢、操作卡顿,用户也会流失。
1.1 网页加载流程
理解性能优化,首先要理解网页的加载流程。
┌─────────────────────────────────────────────────────────┐
│ 网页加载完整流程 │
├─────────────────────────────────────────────────────────┤
│ 1 . DNS 解析(域名 → IP 地址) │
│ ↓ │
│ 2 . TCP 连接(建立客户端 - 服务器的通信通道) │
│ ↓ │
│ 3 . 发送 HTTP 请求(请求 HTML 文件) │
│ ↓ │
│ 4 . 服务器响应(返回 HTML ) │
│ ↓ │
│ 5 . 解析 HTML ,构建 DOM 树 │
│ ↓ │
│ 6 . 解析 CSS,构建 CSSOM 树 │
│ ↓ │
│ 7 . 合并 DOM 和 CSSOM,生成渲染树 │
│ ↓ │
│ 8 . 布局(计算元素位置和大小) │
│ ↓ │
│ 9 . 绘制(绘制像素到屏幕) │
│ ↓ │
│ 10 . 显示页面 │
└─────────────────────────────────────────────────────────┘
性能优化的目标 :缩短每个环节的时间,提升整体加载速度。
1.2 关键性能指标
在优化之前,我们需要知道如何衡量性能。
FP First Paint 首次绘制 < 1s FCP First Contentful Paint 首次内容绘制 < 1.8s LCP Largest Contentful Paint 最大内容绘制 < 2.5s TTI Time to Interactive 可交互时间 < 3.8s CLS Cumulative Layout Shift 累积布局偏移 < 0.1 FID First Input Delay 首次输入延迟 < 100ms
FP :浏览器第一次绘制像素的时间,用户开始看到内容
FCP :浏览器首次绘制文本、图像等有实际内容的时间
LCP :页面中最大可见内容渲染完成的时间(通常是大图或大段文本)
TTI :页面完全可交互的时间(用户可以点击、输入等)
CLS :页面元素在加载过程中意外移动的程度(比如图片加载后把文本往下推)
FID :用户首次与页面交互到浏览器响应的时间
如何查看这些指标?
使用 Chrome 开发者工具的 Lighthouse 面板:
打开 Chrome 开发者工具(F12)
切换到 Lighthouse 标签
点击 Analyze page load
等待分析完成,查看报告
1.3 资源加载优化
1.3.1 减少 HTTP 请求 每个资源文件(HTML、CSS、JS、图片)都需要一个 HTTP 请求,请求越多,加载越慢。
<link rel ="stylesheet" href ="reset.css" >
<link rel ="stylesheet" href ="layout.css" >
<link rel ="stylesheet" href ="theme.css" >
<link rel ="stylesheet" href ="all.css" >
<script src ="utils.js" > </script >
<script src ="api.js" > </script >
<script src ="app.js" > </script >
<script src ="bundle.js" > </script >
方法 2:使用雪碧图(CSS Sprites)
将多个小图标合并成一张大图,通过 CSS background-position 显示不同部分。
.icon {
background-image : url ('sprite.png' );
background-repeat : no-repeat;
display : inline-block;
}
.icon-home {
width : 32px ;
height : 32px ;
background-position : 0 0 ;
}
.icon-user {
width : 32px ;
height : 32px ;
background-position : -32px 0 ;
}
.icon-search {
width : 32px ;
height : 32px ;
background-position : -64px 0 ;
}
1.3.2 压缩资源
<!DOCTYPE html >
<html >
<head >
<title > 我的网页</title >
<link rel ="stylesheet" href ="style.css" >
</head >
<body >
<h1 > 欢迎来到我的网页</h1 >
<p > 这是一段文字</p >
</body >
</html >
<!DOCTYPE html > <html > <head > <title > 我的网页</title > <link rel ="stylesheet" href ="style.css" > </head > <body > <h1 > 欢迎来到我的网页</h1 > <p > 这是一段文字</p > </body > </html >
body {
margin : 0 ;
padding : 0 ;
font-family : Arial, sans-serif;
}
h1 {
color : #333 ;
font-size : 24px ;
}
body {margin :0 ;padding :0 ;font-family :Arial,sans-serif}h1 {color :#333 ;font-size :24px }
JavaScript 压缩 :
变量名缩短、去除空格换行。
function add (a, b ) {
return a + b;
}
const result = add (5 , 3 );
console .log (result);
function add (a,b ){return a+b}const result=add (5 ,3 );console .log (result);
注意 :手动压缩效率低,实际项目中使用构建工具自动完成。
1.3.3 使用 CDN 加速 CDN(Content Delivery Network,内容分发网络)在全球部署服务器,用户从最近的服务器下载资源,加速访问。
<script src ="jquery-3.6.0.js" > </script >
<script src ="https://cdn.jsdelivr.net/npm/[email protected] /dist/jquery.min.js" > </script >
1.3.4 图片优化 图片通常占网页资源的大头,优化图片能显著提升加载速度。
格式 优点 缺点 适用场景 JPEG 压缩率高,文件小 不支持透明背景 照片、复杂图像 PNG 支持透明背景,无损压缩 文件较大 图标、logo、简单图像 WebP 压缩率比 JPEG 高 30% 兼容性稍差 现代浏览器首选 SVG 矢量图,无限放大不失真 不适合复杂图像 图标、logo、插图
<img src ="photo.webp" alt ="照片" >
<picture >
<source srcset ="photo.webp" type ="image/webp" >
<img src ="photo.jpg" alt ="照片" >
</picture >
方法 2:响应式图片
根据设备屏幕大小加载不同尺寸的图片。
<img
src ="small.jpg"
srcset ="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
sizes ="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
alt ="响应式图片"
>
<picture >
<source media ="(max-width: 600px)" srcset ="small.jpg" >
<source media ="(max-width: 1000px)" srcset ="medium.jpg" >
<source media ="(min-width: 1001px)" srcset ="large.jpg" >
<img src ="fallback.jpg" alt ="图片" >
</picture >
方法 3:懒加载(Lazy Loading)
图片只在滚动到可视区域时才加载。
<img src ="placeholder.jpg" loading ="lazy" alt ="懒加载图片" >
<script >
document .addEventListener ('DOMContentLoaded' , function ( ) {
const lazyImages = document .querySelectorAll ('img[data-src]' );
const lazyLoad = function ( ) {
const scrollTop = window .pageYOffset ;
lazyImages.forEach (function (img ) {
if (img.offsetTop < window .innerHeight + scrollTop) {
img.src = img.dataset .src ;
img.classList .remove ('lazy' );
}
});
};
window .addEventListener ('scroll' , lazyLoad);
window .addEventListener ('resize' , lazyLoad);
lazyLoad ();
});
</script >
1.4 代码执行优化
1.4.1 CSS 选择器优化
body header nav ul li a :hover {
color : red;
}
* {
margin : 0 ;
padding : 0 ;
}
input [type="text" ] {
border : 1px solid #ccc ;
}
.nav-link :hover {
color : red;
}
body , h1 , h2 , h3 , p , ul , li {
margin : 0 ;
padding : 0 ;
}
.input-text {
border : 1px solid #ccc ;
}
ID 选择器(#id)
类选择器(.class)
标签选择器(div)
相邻兄弟选择器(div + p)
子选择器(div > p)
后代选择器(div p)
通配符选择器(*)
属性选择器([type="text"])
伪类选择器(:hover)
1.4.2 JavaScript 性能优化
for (let i = 0 ; i < 1000 ; i++) {
document .getElementById ('list' ).innerHTML += '<li>项目' + i + '</li>' ;
}
const fragment = document .createDocumentFragment ();
const list = document .getElementById ('list' );
for (let i = 0 ; i < 1000 ; i++) {
const li = document .createElement ('li' );
li.textContent = '项目' + i;
fragment.appendChild (li);
}
list.appendChild (fragment);
document .querySelectorAll ('.item' ).forEach (function (item ) {
item.addEventListener ('click' , function ( ) {
console .log ('点击了:' + this .textContent );
});
});
document .getElementById ('list' ).addEventListener ('click' , function (e ) {
if (e.target .classList .contains ('item' )) {
console .log ('点击了:' + e.target .textContent );
}
});
防抖(Debounce) :事件触发后,延迟 n 秒再执行,如果 n 秒内再次触发,则重新计时。
应用场景 :搜索框输入、窗口 resize 事件
function debounce (func, delay ) {
let timeoutId;
return function (...args ) {
clearTimeout (timeoutId);
timeoutId = setTimeout (() => {
func.apply (this , args);
}, delay);
};
}
const searchInput = document .getElementById ('search' );
const handleSearch = debounce (function (e ) {
console .log ('搜索:' + e.target .value );
}, 500 );
searchInput.addEventListener ('input' , handleSearch);
节流(Throttle) :事件触发后,立即执行一次,然后在 n 秒内不再执行。
应用场景 :滚动事件、鼠标移动事件
function throttle (func, delay ) {
let lastTime = 0 ;
return function (...args ) {
const now = Date .now ();
if (now - lastTime >= delay) {
func.apply (this , args);
lastTime = now;
}
};
}
window .addEventListener ('scroll' , throttle (function ( ) {
console .log ('页面滚动' );
}, 200 ));
对比 :
用户连续输入:A -> B -> C -> D -> E
时间间隔:0 -> 100ms -> 200ms -> 300ms -> 400ms
延迟:500ms
防抖:输入 A:500ms 后执行;100ms 后输入 B:取消 A 的 500ms,重新计时...
节流:输入 A:立即执行;100ms 后输入 B:忽略(距离上次执行不足 200ms)...
1.5 缓存策略 合理使用缓存可以避免重复下载资源,大幅提升访问速度。
1.5.1 浏览器缓存 强缓存 :
浏览器不向服务器请求,直接使用本地缓存。
Cache-Control: max-age=3600
Expires: Wed, 22 Feb 2026 14:00:00 GMT
协商缓存 :
浏览器向服务器询问资源是否有更新,如果有更新则下载新资源,否则使用本地缓存。
ETag: "abc123"
Last-Modified: Wed, 22 Feb 2026 10:00:00 GMT
If-None-Match: "abc123"
If-Modified-Since: Wed, 22 Feb 2026 10:00:00 GMT
304 Not Modified
200 OK
策略 优点 缺点 适用场景 强缓存 不请求服务器,速度最快 更新不及时 不常变化的静态资源(CSS、JS、图片) 协商缓存 能及时获取更新 需要请求服务器 可能变化的资源(HTML、API 数据)
1.5.2 LocalStorage 缓存 使用 LocalStorage 缓存 API 数据,减少重复请求。
async function fetchUser (id ) {
const cacheKey = 'user_' + id;
const cachedData = localStorage .getItem (cacheKey);
const cacheTime = localStorage .getItem (cacheKey + '_time' );
if (cachedData && cacheTime && (Date .now () - cacheTime < 3600000 )) {
console .log ('使用缓存数据' );
return JSON .parse (cachedData);
}
console .log ('请求新数据' );
const response = await fetch ('https://api.example.com/users/' + id);
const data = await response.json ();
localStorage .setItem (cacheKey, JSON .stringify (data));
localStorage .setItem (cacheKey + '_time' , Date .now ());
return data;
}
fetchUser (123 ).then (user => {
console .log (user);
});
1.5.3 Service Worker 缓存 Service Worker 是浏览器提供的高级缓存技术,可以实现离线访问。
独立于主线程运行
可以拦截网络请求
可以缓存资源
支持离线访问
if ('serviceWorker' in navigator) {
navigator.serviceWorker .register ('/sw.js' )
.then (registration => {
console .log ('Service Worker 注册成功' );
})
.catch (error => {
console .log ('Service Worker 注册失败:' , error);
});
}
const CACHE_NAME = 'my-cache-v1' ;
const urlsToCache = [
'/' ,
'/index.html' ,
'/styles.css' ,
'/script.js' ,
'/image.jpg'
];
self.addEventListener ('install' , event => {
event.waitUntil (
caches.open (CACHE_NAME )
.then (cache => {
return cache.addAll (urlsToCache);
})
);
});
self.addEventListener ('fetch' , event => {
event.respondWith (
caches.match (event.request )
.then (response => {
if (response) {
return response;
}
return fetch (event.request ).then (response => {
const responseToCache = response.clone ();
caches.open (CACHE_NAME )
.then (cache => {
cache.put (event.request , responseToCache);
});
return response;
});
})
);
});
self.addEventListener ('activate' , event => {
event.waitUntil (
caches.keys ().then (cacheNames => {
return Promise .all (
cacheNames.map (cacheName => {
if (cacheName !== CACHE_NAME ) {
return caches.delete (cacheName);
}
})
);
})
);
});
第二部分:代码质量提升
2.1 模块化编程
2.1.1 什么是模块化? 模块化是将代码拆分成独立、可复用的模块,每个模块负责特定的功能。
提高代码可维护性
便于团队协作
避免命名冲突
提高代码复用性
2.1.2 ES6 模块化
export function add (a, b ) {
return a + b;
}
export function multiply (a, b ) {
return a * b;
}
export const PI = 3.14159 ;
export default function subtract (a, b ) {
return a - b;
}
import subtract from './utils.js' ;
import { add, multiply, PI } from './utils.js' ;
import { add as addition } from './utils.js' ;
import * as utils from './utils.js' ;
console .log (add (2 , 3 ));
console .log (addition (2 , 3 ));
console .log (multiply (2 , 3 ));
console .log (PI );
console .log (subtract (5 , 3 ));
console .log (utils.add (2 , 3 ));
<script type ="module" src ="main.js" > </script >
2.1.3 模块化实战:待办事项应用 project/
├── index.html
├── css/
│ └── style.css
├── js/
│ ├── main.js
│ ├── task.js
│ ├── taskService.js
│ └── taskView.js
export class Task {
constructor (id, text, completed = false ) {
this .id = id;
this .text = text;
this .completed = completed;
this .createdAt = new Date ();
}
}
import { Task } from './task.js' ;
export class TaskService {
constructor ( ) {
this .tasks = [];
this .loadTasks ();
}
loadTasks ( ) {
const storedTasks = localStorage .getItem ('tasks' );
if (storedTasks) {
this .tasks = JSON .parse (storedTasks).map (task => {
const newTask = new Task (task.id , task.text , task.completed );
newTask.createdAt = new Date (task.createdAt );
return newTask;
});
}
}
saveTasks ( ) {
localStorage .setItem ('tasks' , JSON .stringify (this .tasks ));
}
addTask (text ) {
const task = new Task (Date .now ().toString (), text);
this .tasks .push (task);
this .saveTasks ();
return task;
}
toggleTask (id ) {
const task = this .tasks .find (t => t.id === id);
if (task) {
task.completed = !task.completed ;
this .saveTasks ();
return task;
}
return null ;
}
deleteTask (id ) {
const index = this .tasks .findIndex (t => t.id === id);
if (index !== -1 ) {
this .tasks .splice (index, 1 );
this .saveTasks ();
return true ;
}
return false ;
}
updateTask (id, newText ) {
const task = this .tasks .find (t => t.id === id);
if (task) {
task.text = newText;
this .saveTasks ();
return task;
}
return null ;
}
getStats ( ) {
const total = this .tasks .length ;
const completed = this .tasks .filter (t => t.completed ).length ;
return { total, completed, active : total - completed };
}
filterTasks (filter ) {
switch (filter) {
case 'active' :
return this .tasks .filter (t => !t.completed );
case 'completed' :
return this .tasks .filter (t => t.completed );
default :
return [...this .tasks ];
}
}
}
import { TaskService } from './taskService.js' ;
export class TaskView {
constructor (taskService ) {
this .taskService = taskService;
this .taskForm = document .getElementById ('taskForm' );
this .taskInput = document .getElementById ('taskInput' );
this .taskList = document .getElementById ('taskList' );
this .taskCount = document .getElementById ('taskCount' );
this .filterButtons = document .querySelectorAll ('.filter-btn' );
this .currentFilter = 'all' ;
this .initEventListeners ();
this .renderTasks ();
this .updateStats ();
}
initEventListeners ( ) {
this .taskForm .addEventListener ('submit' , (e ) => {
e.preventDefault ();
this .handleAddTask ();
});
this .filterButtons .forEach (btn => {
btn.addEventListener ('click' , () => {
this .filterButtons .forEach (b => b.classList .remove ('active' ));
btn.classList .add ('active' );
this .currentFilter = btn.dataset .filter ;
this .renderTasks ();
});
});
this .taskList .addEventListener ('click' , (e ) => {
const taskItem = e.target .closest ('.task-item' );
if (!taskItem) return ;
const taskId = taskItem.dataset .id ;
if (e.target .classList .contains ('task-checkbox' )) {
this .handleToggleTask (taskId);
} else if (e.target .classList .contains ('delete-btn' )) {
this .handleDeleteTask (taskId);
} else if (e.target .classList .contains ('edit-btn' )) {
this .handleEditTask (taskId);
}
});
}
handleAddTask ( ) {
const text = this .taskInput .value .trim ();
if (text) {
const task = this .taskService .addTask (text);
this .renderTask (task);
this .taskInput .value = '' ;
this .updateStats ();
}
}
handleToggleTask (taskId ) {
const task = this .taskService .toggleTask (taskId);
if (task) {
const taskElement = document .querySelector (`.task-item[data-id="${taskId} "]` );
if (taskElement) {
taskElement.classList .toggle ('task-completed' , task.completed );
taskElement.querySelector ('.task-checkbox' ).checked = task.completed ;
this .updateStats ();
}
}
}
handleDeleteTask (taskId ) {
if (confirm ('确定要删除这个任务吗?' )) {
const success = this .taskService .deleteTask (taskId);
if (success) {
const taskElement = document .querySelector (`.task-item[data-id="${taskId} "]` );
if (taskElement) {
taskElement.remove ();
this .updateStats ();
}
}
}
}
handleEditTask (taskId ) {
const task = this .taskService .tasks .find (t => t.id === taskId);
if (task) {
const newText = prompt ('编辑任务' , task.text );
if (newText !== null && newText.trim () !== '' ) {
const updatedTask = this .taskService .updateTask (taskId, newText.trim ());
if (updatedTask) {
const taskElement = document .querySelector (`.task-item[data-id="${taskId} "]` );
if (taskElement) {
taskElement.querySelector ('.task-text' ).textContent = updatedTask.text ;
}
}
}
}
}
renderTask (task ) {
const li = document .createElement ('li' );
li.className = `task-item ${task.completed ? 'task-completed' : '' } ` ;
li.dataset .id = task.id ;
li.innerHTML = `
<input type="checkbox" ${task.completed ? 'checked' : '' } >
<span>${task.text} </span>
<div>
<button>✏️</button>
<button>🗑️</button>
</div>
` ;
this .taskList .appendChild (li);
}
renderTasks ( ) {
this .taskList .innerHTML = '' ;
const filteredTasks = this .taskService .filterTasks (this .currentFilter );
if (filteredTasks.length === 0 ) {
const emptyMessage = document .createElement ('li' );
emptyMessage.textContent = '没有任务' ;
emptyMessage.style .textAlign = 'center' ;
emptyMessage.style .padding = '20px' ;
emptyMessage.style .color = '#777' ;
this .taskList .appendChild (emptyMessage);
return ;
}
filteredTasks.forEach (task => this .renderTask (task));
}
updateStats ( ) {
const stats = this .taskService .getStats ();
this .taskCount .textContent = `任务总数:${stats.total} | 已完成:${stats.completed} | 未完成:${stats.active} ` ;
}
}
import { TaskService } from './js/taskService.js' ;
import { TaskView } from './js/taskView.js' ;
document .addEventListener ('DOMContentLoaded' , () => {
const taskService = new TaskService ();
new TaskView (taskService);
});
<!DOCTYPE html >
<html lang ="zh" >
<head >
<meta charset ="UTF-8" >
<meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
<title > 待办事项应用(模块化版本)</title >
<link rel ="stylesheet" href ="css/style.css" >
</head >
<body >
<div >
<header >
<h1 > 待办事项</h1 >
<p > 任务总数:0 | 已完成:0 | 未完成:0</p >
</header >
<form >
<input type ="text" placeholder ="添加新任务..." required >
<button type ="submit" > 添加</button >
</form >
<div >
<button > 全部</button >
<button > 未完成</button >
<button > 已完成</button >
</div >
<ul > </ul >
</div >
<script type ="module" src ="js/main.js" > </script >
</body >
</html >
职责分离 :每个模块只负责一个功能
易于维护 :修改某个功能只需修改对应的模块
易于测试 :可以单独测试每个模块
代码复用 :模块可以在不同项目中复用
2.2 设计模式 设计模式是解决常见问题的经典方案,掌握设计模式能写出更优雅的代码。
2.2.1 单例模式(Singleton)
class Config {
constructor ( ) {
if (Config .instance ) {
return Config .instance ;
}
this .apiUrl = 'https://api.example.com' ;
this .timeout = 5000 ;
this .debug = true ;
Config .instance = this ;
}
static getInstance ( ) {
if (!Config .instance ) {
Config .instance = new Config ();
}
return Config .instance ;
}
}
const config1 = Config .getInstance ();
const config2 = Config .getInstance ();
console .log (config1 === config2);
console .log (config1.apiUrl );
2.2.2 观察者模式(Observer) 当一个对象的状态发生变化时,所有依赖它的对象都会得到通知。
class EventEmitter {
constructor ( ) {
this .events = {};
}
on (eventName, callback ) {
if (!this .events [eventName]) {
this .events [eventName] = [];
}
this .events [eventName].push (callback);
}
emit (eventName, data ) {
if (this .events [eventName]) {
this .events [eventName].forEach (callback => {
callback (data);
});
}
}
off (eventName, callback ) {
if (this .events [eventName]) {
this .events [eventName] = this .events [eventName].filter (cb => cb !== callback);
}
}
}
const emitter = new EventEmitter ();
emitter.on ('user-login' , (user ) => {
console .log ('用户登录:' , user.name );
});
emitter.on ('user-login' , (user ) => {
console .log ('发送欢迎邮件给:' , user.email );
});
emitter.emit ('user-login' , { name : '张三' , email : '[email protected] ' });
2.2.3 工厂模式(Factory) 应用场景 :创建复杂对象、数据库连接、HTTP 请求
class Button {
constructor (text ) {
this .text = text;
}
render ( ) {
throw new Error ('子类必须实现 render 方法' );
}
}
class PrimaryButton extends Button {
render ( ) {
return `<button>${this .text} </button>` ;
}
}
class SecondaryButton extends Button {
render ( ) {
return `<button>${this .text} </button>` ;
}
}
class DangerButton extends Button {
render ( ) {
return `<button>${this .text} </button>` ;
}
}
function createButton (type, text ) {
switch (type) {
case 'primary' :
return new PrimaryButton (text);
case 'secondary' :
return new SecondaryButton (text);
case 'danger' :
return new DangerButton (text);
default :
throw new Error ('未知的按钮类型:' + type);
}
}
const primaryBtn = createButton ('primary' , '提交' );
const secondaryBtn = createButton ('secondary' , '取消' );
const dangerBtn = createButton ('danger' , '删除' );
console .log (primaryBtn.render ());
console .log (secondaryBtn.render ());
console .log (dangerBtn.render ());
2.2.4 策略模式(Strategy) 定义一系列算法,把它们封装起来,并使它们可以互相替换。
const required = {
validate : (value ) => {
return value.trim () !== '' ;
},
message : '此字段为必填项'
};
const email = {
validate : (value ) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ ;
return regex.test (value);
},
message : '请输入有效的邮箱地址'
};
const minLength = (min ) => ({
validate : (value ) => {
return value.length >= min;
},
message : `最少需要${min} 个字符`
});
const phone = {
validate : (value ) => {
const regex = /^1[3-9]\d{9}$/ ;
return regex.test (value);
},
message : '请输入有效的手机号'
};
class Validator {
constructor ( ) {
this .rules = [];
}
addRule (field, rule ) {
this .rules .push ({ field, rule });
}
validate (data ) {
const errors = {};
for (const { field, rule } of this .rules ) {
if (!rule.validate (data[field])) {
if (!errors[field]) {
errors[field] = [];
}
errors[field].push (rule.message );
}
}
return {
isValid : Object .keys (errors).length === 0 ,
errors
};
}
}
const validator = new Validator ();
validator.addRule ('username' , required);
validator.addRule ('username' , minLength (3 ));
validator.addRule ('email' , required);
validator.addRule ('email' , email);
validator.addRule ('phone' , phone);
const formData = {
username : 'ab' ,
email : 'invalid-email' ,
phone : '12345'
};
const result = validator.validate (formData);
if (!result.isValid ) {
console .log ('验证失败:' );
for (const field in result.errors ) {
console .log (`${field} : ${result.errors[field].join(', ' )} ` );
}
}
2.3 代码规范
2.3.1 命名规范
const a = 10 ;
const b = 20 ;
const c = a + b;
const price = 10 ;
const quantity = 20 ;
const total = price * quantity;
const d = new Date ();
const currentDate = new Date ();
const lastLoginDate = new Date ();
function calc (x, y ) {
return x + y;
}
function calculateTotal (price, quantity ) {
return price * quantity;
}
function getUserById (userId ) {
}
function validateEmail (email ) {
}
function fetchUserData ( ) {
}
class u {
constructor (name ) {
this .n = name;
}
}
class User {
constructor (name ) {
this .name = name;
}
}
class TaskManager {
}
class PaymentService {
}
let flag = true ;
let check = false ;
let status = 1 ;
let isUserLoggedIn = true ;
let hasPermission = false ;
let canEdit = true ;
let shouldDelete = false ;
const max = 100 ;
const timeout = 5000 ;
const MAX_RETRY_COUNT = 3 ;
const API_TIMEOUT = 5000 ;
const DEFAULT_PAGE_SIZE = 20 ;
2.3.2 注释规范
const total = price * quantity;
const total = Math .round (price * quantity);
const users = [];
const users = [];
function add (a, b ) {
return a + b;
}
2.3.3 代码格式
Prettier :代码格式化工具
ESLint :代码检查工具
npm install --save-dev prettier eslint
{
"semi" : true ,
"singleQuote" : true ,
"tabWidth" : 2 ,
"trailingComma" : "es5" ,
"printWidth" : 80
}
npx prettier --write "**/*.{js,css,html}"
npx prettier --check "**/*.{js,css,html}"
npx eslint "**/*.js"
npx eslint "**/*.js" --fix
第三部分:开发工具与工程化 现代前端开发离不开工具链,合理使用工具能大幅提升开发效率。
3.1 包管理器
3.1.1 npm(Node Package Manager) npm 是 Node.js 的包管理器,用于安装和管理 JavaScript 依赖包。
{
"name" : "my-project" ,
"version" : "1.0.0" ,
"description" : "" ,
"main" : "index.js" ,
"scripts" : {
"test" : "echo \"Error: no test specified\" && exit 1"
} ,
"keywords" : [ ] ,
"author" : "" ,
"license" : "ISC"
}
npm install lodash
npm install axios
npm install lodash axios
npm install --save-dev prettier eslint
npm i lodash
npm i -D prettier
{
"dependencies" : {
"axios" : "^1.6.0" ,
"lodash" : "^4.17.21"
} ,
"devDependencies" : {
"eslint" : "^8.50.0" ,
"prettier" : "^3.0.0"
}
}
const _ = require ('lodash' );
const arr = [1 , 2 , 3 , 4 , 5 ];
const sum = _.sum (arr);
console .log (sum);
import _ from 'lodash' ;
npm list
npm list -g
npm update
npm uninstall lodash
npm info lodash
npm search date-fns
3.1.2 yarn 和 pnpm yarn 和 pnpm 是 npm 的替代方案,速度更快。
yarn init -y
yarn add lodash
yarn add --dev prettier
yarn remove lodash
yarn upgrade
yarn install
pnpm init
pnpm add lodash
pnpm add -D prettier
pnpm remove lodash
pnpm install
特性 npm yarn pnpm 安装速度 慢 快 最快 磁盘占用 大 中 小 兼容性 最好 好 较好 生态 最完善 完善 快速发展
3.2 版本控制:Git Git 是分布式版本控制系统,是团队协作的必备工具。
3.2.1 基本概念
仓库(Repository) :存储项目文件和版本历史的地方
提交(Commit) :保存项目的一个快照
分支(Branch) :独立的开发线
合并(Merge) :将分支合并到主分支
3.2.2 常用命令
git add .
git add index.html
git status
git commit -m "feat: 添加待办事项功能"
类型 说明 示例 feat 新功能 feat: 添加用户登录功能 fix 修复 bug fix: 修复登录页面的样式问题 docs 文档更新 docs: 更新 README 文档 style 代码格式调整 style: 调整代码缩进 refactor 重构代码 refactor: 重构用户模块 test 测试相关 test: 添加单元测试 chore 构建/工具 chore: 更新依赖包
git log
git log --oneline
git log --follow index.html
git branch feature/login
git checkout feature/login
git checkout -b feature/login
git branch
git branch -d feature/login
git checkout main
git merge feature/login
git branch -d feature/login
git remote add origin https://github.com/username/repo.git
git push origin main
git push --all origin
git push --tags
git pull origin main
git fetch origin main
git remote -v
3.2.3 .gitignore 文件 .gitignore 文件指定哪些文件不需要被 Git 跟踪。
node_modules/
package-lock.json
dist/
build/
*.min.js
.env
.env.local
.vscode/
.idea/
*.swp
.DS_Store
Thumbs.db
*.log
tmp/
temp/
3.3 构建工具 构建工具用于自动化处理代码转换、压缩、打包等任务。
3.3.1 Webpack npm install --save-dev webpack webpack-cli
const path = require ('path' );
module .exports = {
entry : './src/index.js' ,
output : {
filename : 'bundle.js' ,
path : path.resolve (__dirname, 'dist' )
},
mode : 'development' ,
module : {
rules : [
{
test : /\.css$/ ,
use : ['style-loader' , 'css-loader' ]
},
{
test : /\.(png|jpg|jpeg|gif|svg)$/ ,
type : 'asset/resource'
},
{
test : /\.js$/ ,
exclude : /node_modules/ ,
use : {
loader : 'babel-loader' ,
options : {
presets : ['@babel/preset-env' ]
}
}
}
]
},
plugins : [
new HtmlWebpackPlugin ({
template : './src/index.html' ,
filename : 'index.html'
})
],
devServer : {
static : {
directory : path.join (__dirname, 'dist' )
},
port : 3000 ,
open : true
}
};
npm install --save-dev html-webpack-plugin style-loader css-loader babel-loader @babel/core @babel/preset-env webpack-dev-server
{
"scripts" : {
"dev" : "webpack serve --mode development" ,
"build" : "webpack --mode production"
}
}
npm run dev
npm run build
3.3.2 Vite npm create vite@latest my-project
cd my-project
npm install
特性 Webpack Vite 启动速度 慢(需要打包) 快(按需编译) 热更新 较慢 极快 配置复杂度 高 低 生态 完善 快速发展 适用场景 大型项目 现代项目
3.4 调试技巧
3.4.1 Chrome 开发者工具 Elements 面板 :查看和修改 HTML、CSS
Console 面板 :查看日志、执行 JavaScript
console .log ('普通日志' );
console .log ('用户信息:' , { name : '张三' , age : 25 });
console .error ('发生错误:' , error);
console .warn ('注意:这个功能即将废弃' );
const users = [
{ name : '张三' , age : 25 },
{ name : '李四' , age : 30 }
];
console .table (users);
console .time ('耗时' );
console .timeEnd ('耗时' );
console .group ('用户信息' );
console .log ('姓名:张三' );
console .log ('年龄:25' );
console .groupEnd ();
Application 面板 :查看 LocalStorage、SessionStorage、Cookie 等
3.4.2 断点调试 function calculateSum (a, b ) {
debugger ;
const sum = a + b;
return sum;
}
calculateSum (10 , 20 );
打开 Sources 面板
找到要调试的文件
点击行号设置断点
刷新页面或触发代码执行
使用调试控制按钮(继续、单步进入、单步跳出、单步跳过)
本篇知识总结
性能优化篇 知识点 内容 性能指标 FP、FCP、LCP、TTI、CLS、FID 资源优化 合并文件、压缩、CDN、图片优化、懒加载 代码优化 CSS 选择器优化、DOM 操作优化、事件委托、防抖节流 缓存策略 浏览器缓存、LocalStorage、Service Worker
知识点 内容 模块化 ES6 模块、职责分离、文件组织 设计模式 单例模式、观察者模式、工厂模式、策略模式 代码规范 命名规范、注释规范、代码格式(Prettier、ESLint)
知识点 内容 包管理器 npm、yarn、pnpm 版本控制 Git 基本操作、分支管理、.gitignore 构建工具 Webpack、Vite 调试技巧 Chrome 开发者工具、断点调试
课后练习
练习 1:性能优化
合并 CSS 和 JS 文件
压缩所有资源文件
为图片添加懒加载
使用 CDN 加载 jQuery 或 Vue 等库
使用 Lighthouse 测试优化效果
练习 2:模块化重构
拆分成 Task、TaskService、TaskView 三个模块
使用 ES6 模块化语法
使用 Webpack 或 Vite 构建项目
练习 3:Git 版本控制
初始化 Git 仓库
创建 .gitignore 文件
提交代码到本地仓库
在 GitHub 上创建远程仓库
推送代码到远程仓库
练习 4:使用构建工具
练习 5:性能分析
打开 Lighthouse,生成性能报告
分析 Network 面板,找出加载慢的资源
分析 Performance 面板,找出性能瓶颈
提出优化建议
结语 进阶(一)我们学习了前端性能优化、代码质量提升和工程化实践。这些都是专业前端工程师必备的技能。
如果说基础系列教你'如何写代码',那么进阶系列教你'如何成为专业开发者'。
前端框架(Vue/React)
TypeScript 基础
前端测试
部署与 CI/CD
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online