关注

【前端基础】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 面板:

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

8. 切换到 Lighthouse 标签

9. 点击 Analyze page load

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

1.3 资源加载优化

1.3.1 减少HTTP请求

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

优化方法

方法1:合并CSS和JS文件

<!-- 优化前: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">

<!-- 优化前: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 显示不同部 分。

/* 雪碧图示例 */
.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压缩

<!-- 优化前 -->
<!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压缩

/* 优化前 */
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> <!-- 可能很慢 -->
<!-- 使用CDN加载 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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、插图

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

        方法2:响应式图片

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

<!-- 使用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)

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

<!-- 原生懒加载(现代浏览器支持) -->
<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选择器越复杂,匹配越慢。

        优化前

/* 深度嵌套选择器,性能差 */
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;
}

        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操作

// 优化前:多次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:使用事件委托

// 优化前:为每个元素添加事件监听器,内存占用大
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事件

// 防抖函数
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秒内不再执行。

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

// 节流函数
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,重新计时
  - 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 浏览器缓存

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

        强缓存

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

        实现方式

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

协商缓存:

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

实现方式

# 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数据,减少重复请求。

// 缓存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是浏览器提供的高级缓存技术,可以实现离线访问。

        特点

        • 独立于主线程运行

        • 可以拦截网络请求

        • 可以缓存资源

        • 支持离线访问

        示例

// 注册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)

// 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)

// 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中使用模块

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

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

        文件结构

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

        task.js

// 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

// 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

// 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

// 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

<!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)

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

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

// 单例模式示例
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)

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

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

// 观察者模式示例
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]' });
// 输出:
// 用户登录:张三
// 发送欢迎邮件给:[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 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)

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

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

// 策略模式示例:表单验证
// 策略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 命名规范

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

        变量命名

// 坏的命名
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;
  }
}
// 好的命名:大驼峰命名法(PascalCase)
class User {
  constructor(name) {
    this.name = name;
  }
}
class TaskManager {
  // ...
}
class PaymentService {
  // ...
}

        布尔值命名

// 坏的命名
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;

        常量命名

// 坏的命名
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;
// 好的注释:解释原因
// 使用Math.round避免浮点数精度问题
const total = Math.round(price * quantity);

// 坏的注释:废话
// 定义用户数组
const users = [];
// 好的注释:说明限制条件
// 用户列表最多存储1000条记录,超过后会自动删除最早的记录
const users = [];
// 函数注释:使用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:代码检查工具

        安装

npm install --save-dev prettier eslint

        配置文件 .prettierrc

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80
}

使用

# 格式化所有文件
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依赖包。

        初始化项目

npm init -y

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

{
  "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

        package.json变化

{
  "dependencies": {
    "axios": "^1.6.0",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "eslint": "^8.50.0",
    "prettier": "^3.0.0"
  }
}

        使用依赖包

// 引入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';

        常用命令

# 查看已安装的包
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

npm install -g yarn

        使用yarn

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

        安装pnpm

npm install -g pnpm

        使用pnpm

# 初始化项目
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 常用命令

        初始化仓库

git init

添加文件到暂存区

# 添加所有文件
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
# 合并feature分支
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跟踪。

        示例 .gitignore

# 依赖
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是当前最流行的模块打包工具。

        安装

npm install --save-dev webpack webpack-cli

        基本配置 webpack.config.js

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
  }
};

        安装必要的依赖

npm install --save-dev html-webpack-plugin style-loader css-loader babel-loader @babel/core @babel/preset-env webpack-dev-server

package.json添加脚本:

{
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production"
  }
}

        使用

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

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

        安装

npm create vite@latest my-project
cd my-project
npm install

        开发

npm run dev

        构建

npm run build

        Vite vs Webpack对比

特性

Webpack

Vite

启动速度

慢(需要打包)

快(按需编译)

热更新

较慢

极快

配置复杂度

生态

完善

快速发展

适用场景

大型项目

现代项目

3.4 调试技巧

3.4.1 Chrome开发者工具

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

        Console面板:查看日志、执行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 断点调试

        在代码中设置断点

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

版权声明

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

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/DaiZongFuUp/article/details/158286322

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--