【前端基础】HTML + CSS + JavaScript 进阶(一)

前言

基础系列教你"怎么写代码",进阶系列教你"如何写好代码"。

如果用烹饪来比喻:

• 基础系列:教你认识食材,掌握基本的刀工和火候

• 进阶系列:教你食材搭配、营养搭配、摆盘艺术、厨房管理

这篇进阶文章,我们将聚焦三个核心方向:

  1. 性能优化 :让页面加载更快、运行更流畅

  2. 代码质量 :让代码更易维护、更健壮

  3. 工程化实践 :用现代工具提升开发效率

准备好了吗?让我们从"会写代码"迈向"专业开发"!

第一部分:前端性能优化

性能优化是前端工程师的核心竞争力。一个页面,功能再强大,如果加载慢、操作卡顿,用户也会流失。

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 |

指标说明

  1. FP :浏览器第一次绘制像素的时间,用户开始看到内容

  2. FCP :浏览器首次绘制文本、图像等有实际内容的时间

  3. LCP :页面中最大可见内容渲染完成的时间(通常是大图或大段文 本)

  4. TTI :页面完全可交互的时间(用户可以点击、输入等)

  5. CLS :页面元素在加载过程中意外移动的程度(比如图片加载后把文 本往下推)

  6. FID :用户首次与页面交互到浏览器响应的时间

如何查看这些指标?

使用 Chrome 开发者工具的 Lighthouse 面板:

  1. 打开 Chrome 开发者工具(F12)

  2. 切换到 Lighthouse 标签

  3. 点击 Analyze page load

  4. 等待分析完成,查看报告

1.3 资源加载优化

1.3.1 减少HTTP请求

每个资源文件(HTML、CSS、JS、图片)都需要一个HTTP请求,请求越多,加载越慢。

优化方法

方法1:合并CSS和JS文件

html 复制代码
<!-- 优化前:3个CSS文件 -->
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="layout.css">
<link rel="stylesheet" href="theme.css">
<!-- 优化后:1个合并的CSS文件 -->
<link rel="stylesheet" href="all.css">
html 复制代码
<!-- 优化前:3个JS文件 -->
<script src="utils.js"></script>
<script src="api.js"></script>
<script src="app.js"></script>
<!-- 优化后:1个合并的JS文件 -->
<script src="bundle.js"></script>

方法2:使用雪碧图(CSS Sprites)

将多个小图标合并成一张大图,通过 CSS background-position 显示不同部 分。

css 复制代码
/* 雪碧图示例 */
.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 压缩资源

减小文件大小,加快传输速度。

HTML压缩

html 复制代码
<!-- 优化前 -->
<!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>

CSS压缩

css 复制代码
/* 优化前 */
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压缩

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,内容分发网络)在全球部署服务器,用户从最近的服务器下载资源,加速访问。

示例

html 复制代码
<!-- 本地服务器加载 -->
<script src="jquery-3.6.0.js"></script> <!-- 可能很慢 -->
<!-- 使用CDN加载 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script> <!-- 更快 -->

常用CDN

• cdnjs:https://cdnjs.com/

• jsDelivr:https://www.jsdelivr.com/

• unpkg:https://unpkg.com/

1.3.4 图片优化

图片通常占网页资源的大头,优化图片能显著提升加载速度。

方法1:选择合适的图片格式

|----------|--------------|---------|--------------|
| 格式 | 优点 | 缺点 | 适用场景 |
| JPEG | 压缩率高,文件小 | 不支持透明背景 | 照片、复杂图像 |
| PNG | 支持透明背景,无损压缩 | 文件较大 | 图标、logo、简单图像 |
| WebP | 压缩率比JPEG高30% | 兼容性稍差 | 现代浏览器首选 |
| SVG | 矢量图,无限放大不失真 | 不适合复杂图像 | 图标、logo、插图 |

html 复制代码
<!-- 推荐:使用WebP格式 -->
<img src="photo.webp" alt="照片">
<!-- 备用:为不支持WebP的浏览器提供JPEG -->
<picture>
  <source srcset="photo.webp" type="image/webp">
  <img src="photo.jpg" alt="照片">
</picture>

方法2:响应式图片

根据设备屏幕大小加载不同尺寸的图片。

html 复制代码
<!-- 使用srcset和sizes -->
<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元素 -->
<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)

图片只在滚动到可视区域时才加载。

html 复制代码
<!-- 原生懒加载(现代浏览器支持) -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="懒加载图片">
<!-- JavaScript实现懒加载(兼容性更好) -->
<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选择器优化

CSS选择器越复杂,匹配越慢。

优化前

css 复制代码
/* 深度嵌套选择器,性能差 */
body header nav ul li a:hover {
  color: red;
}
/* 通配符选择器,性能差 */
* {
  margin: 0;
  padding: 0;
}
/* 属性选择器,性能差 */
input[type="text"] {
  border: 1px solid #ccc;
}

优化后

css 复制代码
/* 使用类名,性能好 */
.nav-link:hover {
  color: red;
}
/* 只重置需要的元素 */
body, h1, h2, h3, p, ul, li {
  margin: 0;
  padding: 0;
}
/* 使用类名 */
.input-text {
  border: 1px solid #ccc;
}

CSS选择器性能排名(从快到慢)

  1. ID选择器(#id)

  2. 类选择器(.class)

  3. 标签选择器(div)

  4. 相邻兄弟选择器(div + p)

  5. 子选择器(div > p)

  6. 后代选择器(div p)

  7. 通配符选择器(*)

  8. 属性选择器([type="text"])

  9. 伪类选择器(:hover)

1.4.2 JavaScript性能优化

方法1:减少DOM操作

javascript 复制代码
// 优化前:多次DOM操作,性能差
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);

方法2:使用事件委托

javascript 复制代码
// 优化前:为每个元素添加事件监听器,内存占用大
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);
  }
});

方法3:防抖和节流

防抖(Debounce) :事件触发后,延迟n秒再执行,如果n秒内再次触 发,则重新计时。

应用场景 :搜索框输入、窗口resize事件

javascript 复制代码
// 防抖函数
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);
  // 发送AJAX请求
}, 500);
searchInput.addEventListener('input', handleSearch);

节流(Throttle) :事件触发后,立即执行一次,然后在n秒内不再执行。

应用场景 :滚动事件、鼠标移动事件

javascript 复制代码
// 节流函数
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));

对比

javascript 复制代码
用户连续输入:A -> B -> C -> D -> E
时间间隔:0 -> 100ms -> 200ms -> 300ms -> 400ms
延迟:500ms
防抖:
  - 输入A:500ms后执行
  - 100ms后输入B:取消A的500ms,重新计时
  - 200ms后输入C:取消B的500ms,重新计时
  - 300ms后输入D:取消C的500ms,重新计时
  - 400ms后输入E:取消D的500ms,重新计时
  - 500ms后没有输入:执行E
节流:
  - 输入A:立即执行
  - 100ms后输入B:忽略(距离上次执行不足200ms)
  - 200ms后输入C:执行
  - 300ms后输入D:忽略
  - 400ms后输入E:执行

1.5 缓存策略

合理使用缓存可以避免重复下载资源,大幅提升访问速度。

1.5.1 浏览器缓存

浏览器缓存有两种方式:强缓存和协商缓存。

强缓存

浏览器不向服务器请求,直接使用本地缓存。

实现方式

html 复制代码
# HTTP响应头
Cache-Control: max-age=3600  # 缓存3600秒(1小时)
Expires: Wed, 22 Feb 2026 14:00:00 GMT  # 过期时间(不推荐,优先级低于Cache-Control)

协商缓存:

浏览器向服务器询问资源是否有更新,如果有更新则下载新资源,否则使用本 地缓存。

实现方式

html 复制代码
# HTTP响应头
ETag: "abc123"  # 文件的唯一标识
Last-Modified: Wed, 22 Feb 2026 10:00:00 GMT  # 文件最后修改时间
# HTTP请求头(浏览器自动发送)
If-None-Match: "abc123"  # 上次响应的ETag
If-Modified-Since: Wed, 22 Feb 2026 10:00:00 GMT  # 上次响应的Last-Modified
# HTTP响应(如果资源未修改)
304 Not Modified  # 浏览器使用本地缓存
# HTTP响应(如果资源已修改)
200 OK  # 返回新资源

缓存策略对比

|--------|-------------|---------|----------------------|
| 策略 | 优点 | 缺点 | 适用场景 |
| 强缓存 | 不请求服务器,速度最快 | 更新不及时 | 不常变化的静态资源(CSS、JS、图片) |
| 协商缓存 | 能及时获取更新 | 需要请求服务器 | 可能变化的资源(HTML、API数据) |

1.5.2 LocalStorage缓存

使用LocalStorage缓存API数据,减少重复请求。

javascript 复制代码
// 缓存API数据
async function fetchUser(id) {
  const cacheKey = 'user_' + id;
  const cachedData = localStorage.getItem(cacheKey);
  const cacheTime = localStorage.getItem(cacheKey + '_time');
  // 检查缓存是否存在且未过期(1小时)
  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是浏览器提供的高级缓存技术,可以实现离线访问。

特点

• 独立于主线程运行

• 可以拦截网络请求

• 可以缓存资源

• 支持离线访问

示例

javascript 复制代码
// 注册Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('Service Worker注册成功');
    })
    .catch(error => {
      console.log('Service Worker注册失败:', error);
    });
}

// javascript
// sw.js
const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/script.js',
  '/image.jpg'
];
// 安装Service Worker,缓存资源
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;
        });
      })
  );
});
// 激活Service Worker,清理旧缓存
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)

javascript 复制代码
// utils.js
// 命名导出
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)

javascript 复制代码
// main.js
// 导入默认导出
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));           // 5
console.log(addition(2, 3));      // 5
console.log(multiply(2, 3));      // 6
console.log(PI);                  // 3.14159
console.log(subtract(5, 3));      // 2
console.log(utils.add(2, 3));     // 5

在HTML中使用模块

html 复制代码
<script type="module" src="main.js"></script>
2.1.3 模块化实战:待办事项应用

让我们用模块化重构待办事项应用。

文件结构

html 复制代码
project/
├── index.html
├── css/
│   └── style.css
├── js/
│   ├── main.js           # 入口文件
│   ├── task.js           # Task类
│   ├── taskService.js    # TaskService类
│   └── taskView.js       # TaskView类

task.js

javascript 复制代码
// task.js
export class Task {
  constructor(id, text, completed = false) {
    this.id = id;
    this.text = text;
    this.completed = completed;
    this.createdAt = new Date();
  }
}

taskService.js

javascript 复制代码
// taskService.js
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];
    }
  }
}

taskView.js

javascript 复制代码
// taskView.js
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" class="task-checkbox" ${task.completed ? 'checked' : ''}>
      <span class="task-text">${task.text}</span>
      <div class="task-actions">
        <button class="task-btn edit-btn">✏️</button>
        <button class="task-btn delete-btn">��️</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}`;
  }
}

main.js

javascript 复制代码
// main.js
import { TaskService } from './js/taskService.js';
import { TaskView } from './js/taskView.js';
document.addEventListener('DOMContentLoaded', () => {
  const taskService = new TaskService();
  new TaskView(taskService);
});

index.html

html 复制代码
<!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 class="container">
    <header>
      <h1>待办事项</h1>
      <p id="taskCount">任务总数:0 | 已完成:0 | 未完成:0</p>
    </header>
    <form id="taskForm">
      <input type="text" id="taskInput" placeholder="添加新任务..." required>
      <button type="submit">添加</button>
    </form>
    <div class="filters">
      <button class="filter-btn active" data-filter="all">全部</button>
      <button class="filter-btn" data-filter="active">未完成</button>
      <button class="filter-btn" data-filter="completed">已完成</button>
    </div>
    <ul id="taskList" class="task-list"></ul>
  </div>
  <script type="module" src="js/main.js"></script>
</body>
</html>

模块化的优势

  1. 职责分离 :每个模块只负责一个功能

  2. 易于维护 :修改某个功能只需修改对应的模块

  3. 易于测试 :可以单独测试每个模块

  4. 代码复用 :模块可以在不同项目中复用

2.2 设计模式

设计模式是解决常见问题的经典方案,掌握设计模式能写出更优雅的代码。

2.2.1 单例模式(Singleton)

确保一个类只有一个实例,并提供一个全局访问点。

应用场景 :全局配置、数据库连接、日志管理器

javascript 复制代码
// 单例模式示例
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); // true,是同一个实例
console.log(config1.apiUrl);      // https://api.example.com
2.2.2 观察者模式(Observer)

当一个对象的状态发生变化时,所有依赖它的对象都会得到通知。

应用场景 :事件处理、数据绑定、发布订阅

javascript 复制代码
// 观察者模式示例
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: 'zhangsan@example.com' });
// 输出:
// 用户登录:张三
// 发送欢迎邮件给:zhangsan@example.com
2.2.3 工厂模式(Factory)

定义一个创建对象的接口,让子类决定实例化哪个类。

应用场景 :创建复杂对象、数据库连接、HTTP请求

javascript 复制代码
// 工厂模式示例
class Button {
  constructor(text) {
    this.text = text;
  }
  render() {
    throw new Error('子类必须实现render方法');
  }
}
class PrimaryButton extends Button {
  render() {
    return `<button class="btn btn-primary">${this.text}</button>`;
  }
}
class SecondaryButton extends Button {
  render() {
    return `<button class="btn btn-secondary">${this.text}</button>`;
  }
}
class DangerButton extends Button {
  render() {
    return `<button class="btn btn-danger">${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());    // <button class="btn btn-primary">提交</button>
console.log(secondaryBtn.render());  // <button class="btn btn-secondary">取消</button>
console.log(dangerBtn.render());     // <button class="btn btn-danger">删除</button>
2.2.4 策略模式(Strategy)

定义一系列算法,把它们封装起来,并使它们可以互相替换。

应用场景 :表单验证、排序算法、支付方式

javascript 复制代码
// 策略模式示例:表单验证
// 策略1:必填验证
const required = {
  validate: (value) => {
    return value.trim() !== '';
  },
  message: '此字段为必填项'
};
// 策略2:邮箱验证
const email = {
  validate: (value) => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(value);
  },
  message: '请输入有效的邮箱地址'
};
// 策略3:最小长度验证
const minLength = (min) => ({
  validate: (value) => {
    return value.length >= min;
  },
  message: `最少需要${min}个字符`
});
// 策略4:手机号验证
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(', ')}`);
  }
}
// 输出:
// 验证失败:
// username: 此字段为必填项, 最少需要3个字符
// email: 请输入有效的邮箱地址
// phone: 请输入有效的手机号

2.3 代码规范

2.3.1 命名规范

好的命名让代码自解释,无需注释。

变量命名

javascript 复制代码
// 坏的命名
const a = 10;
const b = 20;
const c = a + b;
// 好的命名
const price = 10;
const quantity = 20;
const total = price * quantity;
javascript 复制代码
// 坏的命名
const d = new Date();
// 好的命名
const currentDate = new Date();
const lastLoginDate = new Date();

函数命名

javascript 复制代码
// 坏的命名
function calc(x, y) {
  return x + y;
}
// 好的命名:动词+名词
function calculateTotal(price, quantity) {
  return price * quantity;
}
function getUserById(userId) {
  // ...
}
function validateEmail(email) {
  // ...
}
function fetchUserData() {
  // ...
}

类命名

javascript 复制代码
// 坏的命名
class u {
  constructor(name) {
    this.n = name;
  }
}
// 好的命名:大驼峰命名法(PascalCase)
class User {
  constructor(name) {
    this.name = name;
  }
}
class TaskManager {
  // ...
}
class PaymentService {
  // ...
}

布尔值命名

javascript 复制代码
// 坏的命名
let flag = true;
let check = false;
let status = 1;
// 好的命名:使用is、has、can、should等前缀
let isUserLoggedIn = true;
let hasPermission = false;
let canEdit = true;
let shouldDelete = false;

常量命名

javascript 复制代码
// 坏的命名
const max = 100;
const timeout = 5000;
// 好的命名:全大写+下划线
const MAX_RETRY_COUNT = 3;
const API_TIMEOUT = 5000;
const DEFAULT_PAGE_SIZE = 20;
2.3.2 注释规范

注释应该解释"为什么",而不是"是什么"。

javascript 复制代码
// 坏的注释:重复代码
// 计算总价
const total = price * quantity;
// 好的注释:解释原因
// 使用Math.round避免浮点数精度问题
const total = Math.round(price * quantity);
javascript 复制代码
// 坏的注释:废话
// 定义用户数组
const users = [];
// 好的注释:说明限制条件
// 用户列表最多存储1000条记录,超过后会自动删除最早的记录
const users = [];
javascript 复制代码
// 函数注释:使用JSDoc格式
/**
 * 计算两个数的和
 * @param {number} a - 第一个数
 * @param {number} b - 第二个数
 * @returns {number} 两数之和
 * @example
 * add(2, 3) // 5
 */
function add(a, b) {
  return a + b;
}
2.3.3 代码格式

使用代码格式化工具保证代码风格统一。

推荐工具

Prettier :代码格式化工具

ESLint :代码检查工具

安装

bash 复制代码
npm install --save-dev prettier eslint

配置文件 .prettierrc

bash 复制代码
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80
}

使用

bash 复制代码
# 格式化所有文件
npx prettier --write "**/*.{js,css,html}"
# 检查代码格式
npx prettier --check "**/*.{js,css,html}"
# ESLint检查
npx eslint "**/*.js"
# ESLint自动修复
npx eslint "**/*.js" --fix

第三部分:开发工具与工程化

现代前端开发离不开工具链,合理使用工具能大幅提升开发效率。

3.1 包管理器

3.1.1 npm(Node Package Manager)

npm是Node.js的包管理器,用于安装和管理JavaScript依赖包。

初始化项目

bash 复制代码
npm init -y

这会创建一个 package.json 文件:

bash 复制代码
{
  "name": "my-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

安装依赖包

bash 复制代码
# 安装生产环境依赖
npm install lodash
npm install axios
# 一次性安装多个包
npm install lodash axios
# 安装开发环境依赖
npm install --save-dev prettier eslint
# 简写
npm i lodash
npm i -D prettier

package.json变化

bash 复制代码
{
  "dependencies": {
    "axios": "^1.6.0",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "eslint": "^8.50.0",
    "prettier": "^3.0.0"
  }
}

使用依赖包

javascript 复制代码
// 引入lodash
const _ = require('lodash');
// 使用lodash
const arr = [1, 2, 3, 4, 5];
const sum = _.sum(arr);
console.log(sum); // 15
// 在模块化项目中使用ES6 import
import _ from 'lodash';

常用命令

bash 复制代码
# 查看已安装的包
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

bash 复制代码
npm install -g yarn

使用yarn

bash 复制代码
# 初始化项目
yarn init -y
# 安装依赖
yarn add lodash
yarn add --dev prettier
# 删除依赖
yarn remove lodash
# 更新依赖
yarn upgrade
# 安装所有依赖
yarn install

安装pnpm

bash 复制代码
npm install -g pnpm

使用pnpm

bash 复制代码
# 初始化项目
pnpm init
# 安装依赖
pnpm add lodash
pnpm add -D prettier
# 删除依赖
pnpm remove lodash
# 安装所有依赖
pnpm install

npm、yarn、pnpm对比

|--------|---------|----------|----------|
| 特性 | npm | yarn | pnpm |
| 安装速度 | 慢 | 快 | 最快 |
| 磁盘占用 | 大 | 中 | 小 |
| 兼容性 | 最好 | 好 | 较好 |
| 生态 | 最完善 | 完善 | 快速发展 |

3.2 版本控制:Git

Git是分布式版本控制系统,是团队协作的必备工具。

3.2.1 基本概念

仓库(Repository) :存储项目文件和版本历史的地方

提交(Commit) :保存项目的一个快照

分支(Branch) :独立的开发线

合并(Merge) :将分支合并到主分支

3.2.2 常用命令

初始化仓库

bash 复制代码
git init

添加文件到暂存区

bash 复制代码
# 添加所有文件
git add .
# 添加指定文件
git add index.html
# 查看暂存区状态
git status

提交到本地仓库

bash 复制代码
git commit -m "feat: 添加待办事项功能"

提交信息规范

|----------|--------|------------------|
| 类型 | 说明 | 示例 |
| feat | 新功能 | feat: 添加用户登录功能 |
| fix | 修复bug | fix: 修复登录页面的样式问题 |
| docs | 文档更新 | docs: 更新README文档 |
| style | 代码格式调整 | style: 调整代码缩进 |
| refactor | 重构代码 | refactor: 重构用户模块 |
| test | 测试相关 | test: 添加单元测试 |
| chore | 构建/工具 | chore: 更新依赖包 |

查看提交历史

bash 复制代码
# 查看提交历史
git log
# 查看简洁的提交历史
git log --oneline
# 查看某个文件的修改历史
git log --follow index.html

创建和切换分支

bash 复制代码
# 创建分支
git branch feature/login
# 切换分支
git checkout feature/login
# 创建并切换分支(简写)
git checkout -b feature/login
# 查看所有分支
git branch
# 删除分支
git branch -d feature/login

合并分支

bash 复制代码
# 切换到主分支
git checkout main
# 合并feature分支
git merge feature/login
# 删除已合并的分支
git branch -d feature/login

推送到远程仓库

bash 复制代码
# 关联远程仓库
git remote add origin https://github.com/username/repo.git
# 推送到远程仓库
git push origin main
# 推送所有分支
git push --all origin
# 推送标签
git push --tags

从远程仓库拉取

bash 复制代码
# 拉取远程更新
git pull origin main
# 获取远程更新但不合并
git fetch origin main
# 查看远程仓库
git remote -v
3.2.3 .gitignore文件

.gitignore文件指定哪些文件不需要被Git跟踪。

示例 .gitignore

bash 复制代码
# 依赖
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

Webpack是当前最流行的模块打包工具。

安装

bash 复制代码
npm install --save-dev webpack webpack-cli

基本配置 webpack.config.js

javascript 复制代码
const path = require('path');
module.exports = {
  // 入口文件
  entry: './src/index.js',
  // 输出配置
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // 模式
  mode: 'development',
  // 加载器
  module: {
    rules: [
      // 处理CSS
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      // 处理图片
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        type: 'asset/resource'
      },
      // 转换ES6+
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  // 插件
  plugins: [
    // HTML模板插件
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html'
    })
  ],
  // 开发服务器
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist')
    },
    port: 3000,
    open: true
  }
};

安装必要的依赖

bash 复制代码
npm install --save-dev html-webpack-plugin style-loader css-loader babel-loader @babel/core @babel/preset-env webpack-dev-server

package.json添加脚本:
bash 复制代码
{
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production"
  }
}

使用

bash 复制代码
# 开发模式(启动开发服务器)
npm run dev
# 生产模式(打包)
npm run build
3.3.2 Vite

Vite是新一代构建工具,速度更快。

安装

bash 复制代码
npm create vite@latest my-project
cd my-project
npm install

开发

bash 复制代码
npm run dev

构建

bash 复制代码
npm run build

Vite vs Webpack对比

|--------|-------------|----------|
| 特性 | Webpack | Vite |
| 启动速度 | 慢(需要打包) | 快(按需编译) |
| 热更新 | 较慢 | 极快 |
| 配置复杂度 | 高 | 低 |
| 生态 | 完善 | 快速发展 |
| 适用场景 | 大型项目 | 现代项目 |

3.4 调试技巧

3.4.1 Chrome开发者工具

Elements面板 :查看和修改HTML、CSS

Console面板 :查看日志、执行JavaScript

javascript 复制代码
// console.log:输出普通信息
console.log('普通日志');
console.log('用户信息:', { name: '张三', age: 25 });
// console.error:输出错误信息
console.error('发生错误:', error);
// console.warn:输出警告信息
console.warn('注意:这个功能即将废弃');
// console.table:以表格形式输出对象数组
const users = [
  { name: '张三', age: 25 },
  { name: '李四', age: 30 }
];
console.table(users);
// console.time / console.timeEnd:测量代码执行时间
console.time('耗时');
// 执行一些代码
console.timeEnd('耗时');
// console.group / console.groupEnd:分组输出
console.group('用户信息');
console.log('姓名:张三');
console.log('年龄:25');
console.groupEnd();

Network面板 :查看网络请求

Application面板 :查看LocalStorage、SessionStorage、Cookie等

Performance面板 :分析页面性能

3.4.2 断点调试

在代码中设置断点

javascript 复制代码
function calculateSum(a, b) {
  debugger; // 在这里设置断点,代码执行会暂停
  const sum = a + b;
  return sum;
}
calculateSum(10, 20);

在开发者工具中设置断点

  1. 打开Sources面板

  2. 找到要调试的文件

  3. 点击行号设置断点

  4. 刷新页面或触发代码执行

  5. 使用调试控制按钮(继续、单步进入、单步跳出、单步跳过)

本篇知识总结

性能优化篇

|---------|-----------------------------------|
| 知识点 | 内容 |
| 性能指标 | FP、FCP、LCP、TTI、CLS、FID |
| 资源优化 | 合并文件、压缩、CDN、图片优化、懒加载 |
| 代码优化 | CSS选择器优化、DOM操作优化、事件委托、防抖节流 |
| 缓存策略 | 浏览器缓存、LocalStorage、Service Worker |

代码质量篇

|---------|---------------------------------|
| 知识点 | 内容 |
| 模块化 | ES6模块、职责分离、文件组织 |
| 设计模式 | 单例模式、观察者模式、工厂模式、策略模式 |
| 代码规范 | 命名规范、注释规范、代码格式(Prettier、ESLint) |

工程化篇

|---------|-------------------------|
| 知识点 | 内容 |
| 包管理器 | npm、yarn、pnpm |
| 版本控制 | Git基本操作、分支管理、.gitignore |
| 构建工具 | Webpack、Vite |
| 调试技巧 | Chrome开发者工具、断点调试 |

课后练习

练习1:性能优化

找一个简单的网页,完成以下优化:

  1. 合并CSS和JS文件

  2. 压缩所有资源文件

  3. 为图片添加懒加载

  4. 使用CDN加载jQuery或Vue等库

  5. 使用Lighthouse测试优化效果

练习2:模块化重构

将之前写的待办事项应用重构为模块化结构:

  1. 拆分成Task、TaskService、TaskView三个模块

  2. 使用ES6模块化语法

  3. 使用Webpack或Vite构建项目

练习3:Git版本控制

为你的项目添加Git版本控制:

  1. 初始化Git仓库

  2. 创建.gitignore文件

  3. 提交代码到本地仓库

  4. 在GitHub上创建远程仓库

  5. 推送代码到远程仓库

练习4:使用构建工具

使用Vite创建一个新项目:

  1. 安装Vite

  2. 创建项目

  3. 配置项目

  4. 开发和构建

练习5:性能分析

使用Chrome开发者工具分析一个网站:

  1. 打开Lighthouse,生成性能报告

  2. 分析Network面板,找出加载慢的资源

  3. 分析Performance面板,找出性能瓶颈

  4. 提出优化建议

结语

进阶(一)我们学习了前端性能优化、代码质量提升和工程化实践。这些都是专业前端工程师必备的技能。

如果说基础系列教你"如何写代码",那么进阶系列教你"如何成为专业开发者"。

下一篇进阶文章,我们将深入学习:

• 前端框架(Vue/React)

• TypeScript基础

• 前端测试

• 部署与CI/CD

版权声明

本文为原创文章,转载请注明出处。未经作者许可,禁止用于商业用途。

相关推荐
xyq20241 小时前
Shell echo命令详解
开发语言
不染尘.2 小时前
字符串哈希
开发语言·数据结构·c++·算法·哈希算法
qq_24218863322 小时前
【零基础使用Trae CN编写第一个AI游戏教程】
开发语言·前端·人工智能·python·游戏·html
a1117762 小时前
3D赛车躲避游戏(html threeJS开源)
前端·游戏·3d·开源·html·threejs
PD我是你的真爱粉2 小时前
Vue Router 4 路由进阶
前端·javascript·vue.js
木子欢儿2 小时前
在 Debian 13(以及 12)上安装和配置 tightvncserver 并让普通用户使
运维·前端·debian
浅念-2 小时前
C++ STL stack、queue 与容器适配器详解
开发语言·c++·经验分享·笔记·学习·面试
SakitamaX2 小时前
Nginx安装与实验
服务器·前端·nginx
赵谨言2 小时前
基于Python的汽车CAN总线报文格式转换系统的设计与实现
大数据·开发语言·经验分享·笔记·python