普通视图

发现新文章,点击刷新页面。
昨天 — 2025年10月30日首页

2025年,我为什么建议你先学React再学Vue?

2025年10月30日 07:27

你是不是刚准备入门前端开发,面对React和Vue两个热门框架却不知道如何选择?

看着招聘网站上React和Vue的职位要求,担心选错方向影响未来发展?

别担心,这篇文章就是为你准备的。我会用最直白的语言,带你快速体验两大框架的魅力,并告诉你为什么在2025年的今天,我强烈建议从React开始学起。

读完本文,你将获得两大框架的完整入门指南,还有可以直接复用的代码示例,帮你节省大量摸索时间。

先来看看React:简洁就是美

React的核心思想非常直接——用JavaScript构建用户界面。它不会强迫你学习太多新概念,而是充分利用你已经掌握的JavaScript知识。

让我们看一个最简单的计数器组件:

// 引入React和useState钩子
import React, { useState } from 'react';

// 定义计数器组件
function Counter() {
  // useState是React的核心特性,用于管理组件状态
  // count是当前状态值,setCount是更新状态的函数
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点击了 {count} 次</p>
      {/* 点击按钮时调用setCount更新状态 */}
      <button onClick={() => setCount(count + 1)}>
        点我加一
      </button>
    </div>
  );
}

export default Counter;

这段代码展示了React的几个关键特点:组件化、状态管理、声明式编程。你发现了吗?几乎就是纯JavaScript,加上一点类似HTML的JSX语法。

React的学习曲线相对平缓,因为你主要是在写JavaScript。这也是为什么很多公司在新项目中仍然首选React——它更接近编程的本质。

再看看Vue:贴心但需要适应

Vue的设计哲学完全不同,它提供了一套更完整的解决方案,包括模板语法、响应式系统等。

同样的计数器,用Vue 3的Composition API实现:

<template>
  <div>
    <p>你点击了 {{ count }} 次</p>
    <!-- 模板语法更接近原生HTML -->
    <button @click="increment">
      点我加一
    </button>
  </div>
</template>

<script setup>
// 引入ref函数
import { ref } from 'vue'

// 定义响应式数据
const count = ref(0)

// 定义方法
const increment = () => {
  count.value++
}
</script>

Vue的模板语法对初学者很友好,特别是如果你有HTML基础。但注意看,这里出现了新的概念:ref、.value、@click指令等。Vue创造了自己的一套规则,你需要先理解这些概念才能上手。

为什么我推荐先学React?

在2025年的今天,前端技术生态已经相当成熟。基于我的观察和实际项目经验,有三个理由支持先学React:

就业机会更多:打开任何招聘平台,React的职位数量通常是Vue的1.5-2倍。大型科技公司更倾向于使用React,这意味着更好的职业发展空间。

技术迁移成本低:学完React后,你会发现很多概念在其他框架中也通用。状态管理、组件化思想、虚拟DOM等知识都是可以迁移的。反过来,从Vue转到React会困难一些。

更接近现代JavaScript:React鼓励你使用最新的JavaScript特性,而不是框架特定的语法。这对你的长远发展更有帮助,毕竟框架会过时,但JavaScript不会。

真实项目中的代码对比

让我们看一个更实际的例子:用户列表组件。

React版本:

import React, { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  // useEffect处理副作用
  useEffect(() => {
    fetchUsers();
  }, []);

  const fetchUsers = async () => {
    try {
      const response = await fetch('/api/users');
      const data = await response.json();
      setUsers(data);
    } catch (error) {
      console.error('获取用户失败:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>加载中...</div>;

  return (
    <div>
      <h2>用户列表</h2>
      {users.map(user => (
        <div key={user.id}>
          <span>{user.name}</span>
          <span>{user.email}</span>
        </div>
      ))}
    </div>
  );
}

Vue版本:

<template>
  <div>
    <div v-if="loading">加载中...</div>
    <div v-else>
      <h2>用户列表</h2>
      <div v-for="user in users" :key="user.id">
        <span>{{ user.name }}</span>
        <span>{{ user.email }}</span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const users = ref([])
const loading = ref(true)

const fetchUsers = async () => {
  try {
    const response = await fetch('/api/users')
    const data = await response.json()
    users.value = data
  } catch (error) {
    console.error('获取用户失败:', error)
  } finally {
    loading.value = false
  }
}

// onMounted是生命周期钩子
onMounted(() => {
  fetchUsers()
})
</script>

注意到区别了吗?React更倾向于用JavaScript解决问题,而Vue提供了更多专用语法。从长远看,深入理解JavaScript比掌握框架语法更有价值。

学习路径建议

如果你决定接受我的建议从React开始,这是最有效的学习路径:

第一周:掌握React基础概念。JSX语法、组件定义、props传递、useState状态管理。不要急着学太多,把基础打牢固。

第二周:深入学习Hooks。useEffect、useContext、useReducer,理解React的数据流和生命周期。

第三周:构建完整项目。找一个实际需求,比如个人博客或者待办事项应用,把学到的知识用起来。

第四周:学习状态管理。了解Redux Toolkit或者Zustand,理解在复杂应用中如何管理状态。

完成这个月的学习后,你再回头看Vue,会发现很多概念都是相通的,学习成本大大降低。

但Vue就一无是处吗?

绝对不是。Vue在某些场景下表现非常出色:

如果你要快速开发中小型项目,Vue的完整生态和约定式配置能显著提升开发效率。

如果你的团队中新手开发者较多,Vue的模板语法和学习曲线确实更容易上手。

在2025年,Vue 3的Composition API让代码组织更加灵活,性能也相当优秀。它仍然是一个很棒的选择,只是从学习路径和职业发展的角度,我更推荐先掌握React。

实际开发中的小技巧

无论你选择哪个框架,这些技巧都能帮你少走弯路:

代码组织:保持组件小而专一。如果一个组件超过100行,考虑拆分。

状态管理:不要过度设计。先从useState开始,真正需要时再引入状态管理库。

性能优化:使用React.memo或Vue的computed属性避免不必要的重新渲染,但不要过早优化。

错误处理:一定要有错误边界,给用户友好的错误提示而不是白屏。

下一步该怎么做?

现在你应该对两大框架有了基本认识。我的建议是:

今天就创建一个React项目,把文章中的计数器例子跑起来。不要只看不练,亲手写代码的感觉完全不同。

遇到问题时,记住这是学习过程的正常部分。React和Vue都有优秀的官方文档和活跃的社区,你遇到的问题很可能已经有人解决过了。

学习框架只是开始,更重要的是理解背后的编程思想和设计模式。这些知识会让你在任何技术变革中都能快速适应。

技术会更新,生态会变化,但解决问题的能力才是你真正的核心竞争力。

昨天以前首页

前端别再乱存数据了!这3种存储方案让你的应用快如闪电

2025年10月28日 07:39

你是不是也遇到过这样的场景?

用户刚填完一个超长的表单,不小心刷新了页面,所有数据都没了... 从接口请求的数据,用户每次操作都要重新加载,体验卡成PPT... 应用离线状态下完全无法使用,用户直接流失...

别担心!今天我就带你彻底解决这些问题。看完这篇文章,你将掌握一套完整的数据交互方案,让你的应用在任何网络状态下都能流畅运行。

为什么数据存储这么重要?

想象一下,你去超市购物,每次想买什么东西,都要跑回家查一下购物清单,然后再跑回超市... 这得多累啊!

网页应用也是同样的道理。合理的数据存储就像你的购物清单,把需要的东西记下来,随用随取,效率直接翻倍。

先来看看我们最常用的数据获取方式——Fetch API

Fetch API:现代前端的数据搬运工

Fetch API 是现在最主流的数据请求方式,比老旧的 XMLHttpRequest 好用太多了。它基于 Promise,写起来特别优雅。

// 最基本的 GET 请求
async function fetchUserData(userId) {
  try {
    // 发起请求,等待响应
    const response = await fetch(`https://api.example.com/users/${userId}`);
    
    // 检查响应是否成功(状态码 200-299)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    // 解析 JSON 数据
    const userData = await response.json();
    return userData;
  } catch (error) {
    // 统一的错误处理
    console.error('获取用户数据失败:', error);
    throw error;
  }
}

// 使用示例
fetchUserData(123)
  .then(user => {
    console.log('用户信息:', user);
    // 在这里更新页面显示
  })
  .catch(error => {
    // 显示错误提示给用户
    alert('加载用户信息失败,请重试');
  });

但光会请求数据还不够,聪明的开发者都知道:好的数据要懂得缓存。这就引出了我们的主角——本地存储。

本地存储三剑客:sessionStorage、localStorage、IndexedDB

1. sessionStorage:短暂的记忆

sessionStorage 就像你的短期记忆,页面会话结束时数据就清空了。适合存储一些临时数据。

// 保存表单草稿
function saveFormDraft(formData) {
  // 将对象转换为 JSON 字符串存储
  sessionStorage.setItem('formDraft', JSON.stringify(formData));
  console.log('表单草稿已保存');
}

// 读取表单草稿
function loadFormDraft() {
  const draft = sessionStorage.getItem('formDraft');
  if (draft) {
    // 将 JSON 字符串解析回对象
    return JSON.parse(draft);
  }
  return null;
}

// 清除草稿
function clearFormDraft() {
  sessionStorage.removeItem('formDraft');
  console.log('表单草稿已清除');
}

// 使用示例:页面加载时恢复草稿
window.addEventListener('load', () => {
  const draft = loadFormDraft();
  if (draft) {
    // 用草稿数据填充表单
    document.getElementById('username').value = draft.username || '';
    document.getElementById('email').value = draft.email || '';
    console.log('表单草稿已恢复');
  }
});

// 输入时实时保存
document.getElementById('myForm').addEventListener('input', (event) => {
  const formData = {
    username: document.getElementById('username').value,
    email: document.getElementById('email').value
  };
  saveFormDraft(formData);
});

2. localStorage:持久的仓库

localStorage 是长期存储,除非主动清除,否则数据会一直存在。适合存储用户偏好设置等。

// 用户主题偏好管理
class ThemeManager {
  constructor() {
    this.currentTheme = this.getSavedTheme() || 'light';
    this.applyTheme(this.currentTheme);
  }
  
  // 获取保存的主题
  getSavedTheme() {
    return localStorage.getItem('userTheme');
  }
  
  // 保存主题偏好
  saveTheme(theme) {
    localStorage.setItem('userTheme', theme);
    this.currentTheme = theme;
    console.log(`主题已保存: ${theme}`);
  }
  
  // 应用主题
  applyTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
    this.saveTheme(theme);
  }
  
  // 切换主题
  toggleTheme() {
    const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
    this.applyTheme(newTheme);
  }
  
  // 清除主题设置
  clearTheme() {
    localStorage.removeItem('userTheme');
    this.currentTheme = 'light';
    this.applyTheme('light');
    console.log('主题设置已清除');
  }
}

// 使用示例
const themeManager = new ThemeManager();

// 主题切换按钮
document.getElementById('themeToggle').addEventListener('click', () => {
  themeManager.toggleTheme();
});

3. IndexedDB:大数据专家

当你的数据量很大,或者需要复杂查询时,IndexedDB 就是最佳选择。

// 创建一个简单的数据库管理器
class DBManager {
  constructor(dbName, version) {
    this.dbName = dbName;
    this.version = version;
    this.db = null;
  }
  
  // 打开数据库
  async open() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };
      
      // 第一次创建数据库时初始化结构
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // 创建用户表
        if (!db.objectStoreNames.contains('users')) {
          const store = db.createObjectStore('users', { keyPath: 'id' });
          // 创建索引,方便按姓名搜索
          store.createIndex('name', 'name', { unique: false });
        }
        
        // 创建文章表
        if (!db.objectStoreNames.contains('articles')) {
          const store = db.createObjectStore('articles', { keyPath: 'id' });
          store.createIndex('title', 'title', { unique: false });
          store.createIndex('createdAt', 'createdAt', { unique: false });
        }
      };
    });
  }
  
  // 添加数据
  async add(storeName, data) {
    const transaction = this.db.transaction([storeName], 'readwrite');
    const store = transaction.objectStore(storeName);
    
    return new Promise((resolve, reject) => {
      const request = store.add(data);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }
  
  // 获取所有数据
  async getAll(storeName) {
    const transaction = this.db.transaction([storeName], 'readonly');
    const store = transaction.objectStore(storeName);
    
    return new Promise((resolve, reject) => {
      const request = store.getAll();
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }
  
  // 按索引查询
  async getByIndex(storeName, indexName, value) {
    const transaction = this.db.transaction([storeName], 'readonly');
    const store = transaction.objectStore(storeName);
    const index = store.index(indexName);
    
    return new Promise((resolve, reject) => {
      const request = index.getAll(value);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }
}

// 使用示例
async function initDB() {
  const dbManager = new DBManager('MyAppDB', 1);
  await dbManager.open();
  
  // 添加示例用户
  await dbManager.add('users', {
    id: 1,
    name: '张三',
    email: 'zhangsan@example.com',
    createdAt: new Date()
  });
  
  // 获取所有用户
  const users = await dbManager.getAll('users');
  console.log('所有用户:', users);
  
  return dbManager;
}

// 初始化数据库
initDB().then(dbManager => {
  console.log('数据库初始化完成');
});

实战:构建智能数据缓存系统

现在让我们把 Fetch API 和本地存储结合起来,打造一个真正智能的数据缓存系统。

// 智能数据管理器
class SmartDataManager {
  constructor() {
    this.cache = new Map(); // 内存缓存
  }
  
  // 获取数据(带缓存)
  async getData(url, options = {}) {
    const {
      cacheKey = url,           // 缓存键名
      cacheTime = 5 * 60 * 1000, // 默认缓存5分钟
      forceRefresh = false      // 强制刷新
    } = options;
    
    // 检查内存缓存
    if (!forceRefresh) {
      const cached = this.getFromCache(cacheKey, cacheTime);
      if (cached) {
        console.log('从内存缓存返回数据');
        return cached;
      }
      
      // 检查 localStorage 缓存
      const stored = this.getFromStorage(cacheKey, cacheTime);
      if (stored) {
        console.log('从本地存储返回数据');
        // 同时更新内存缓存
        this.cache.set(cacheKey, {
          data: stored,
          timestamp: Date.now()
        });
        return stored;
      }
    }
    
    // 缓存中没有,从接口获取
    console.log('从接口获取数据');
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
      
      const data = await response.json();
      
      // 同时更新内存缓存和本地存储
      this.setCache(cacheKey, data);
      this.setStorage(cacheKey, data);
      
      return data;
    } catch (error) {
      console.error('获取数据失败:', error);
      throw error;
    }
  }
  
  // 从内存缓存获取
  getFromCache(key, cacheTime) {
    const cached = this.cache.get(key);
    if (cached && Date.now() - cached.timestamp < cacheTime) {
      return cached.data;
    }
    return null;
  }
  
  // 从本地存储获取
  getFromStorage(key, cacheTime) {
    try {
      const stored = localStorage.getItem(`cache_${key}`);
      if (stored) {
        const { data, timestamp } = JSON.parse(stored);
        if (Date.now() - timestamp < cacheTime) {
          return data;
        } else {
          // 缓存过期,清理
          localStorage.removeItem(`cache_${key}`);
        }
      }
    } catch (error) {
      console.warn('读取缓存失败:', error);
    }
    return null;
  }
  
  // 设置内存缓存
  setCache(key, data) {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }
  
  // 设置本地存储
  setStorage(key, data) {
    try {
      localStorage.setItem(`cache_${key}`, JSON.stringify({
        data,
        timestamp: Date.now()
      }));
    } catch (error) {
      console.warn('存储缓存失败:', error);
      // 如果存储失败(比如超出容量),清理最旧的缓存
      this.cleanupStorage();
    }
  }
  
  // 清理过期缓存
  cleanupStorage() {
    const keysToRemove = [];
    
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key.startsWith('cache_')) {
        try {
          const stored = JSON.parse(localStorage.getItem(key));
          // 删除超过1天的缓存
          if (Date.now() - stored.timestamp > 24 * 60 * 60 * 1000) {
            keysToRemove.push(key);
          }
        } catch (error) {
          // 数据格式错误,直接删除
          keysToRemove.push(key);
        }
      }
    }
    
    keysToRemove.forEach(key => localStorage.removeItem(key));
  }
  
  // 清除指定缓存
  clearCache(key) {
    this.cache.delete(key);
    localStorage.removeItem(`cache_${key}`);
  }
  
  // 清除所有缓存
  clearAllCache() {
    this.cache.clear();
    Object.keys(localStorage)
      .filter(key => key.startsWith('cache_'))
      .forEach(key => localStorage.removeItem(key));
  }
}

// 使用示例
const dataManager = new SmartDataManager();

// 获取用户列表(带缓存)
async function loadUsers() {
  try {
    const users = await dataManager.getData('/api/users', {
      cacheKey: 'user_list',
      cacheTime: 10 * 60 * 1000 // 缓存10分钟
    });
    
    // 渲染用户列表
    renderUserList(users);
  } catch (error) {
    // 显示错误状态
    showError('加载用户列表失败');
  }
}

// 强制刷新数据
async function refreshUsers() {
  try {
    const users = await dataManager.getData('/api/users', {
      cacheKey: 'user_list',
      forceRefresh: true // 强制从接口获取最新数据
    });
    
    renderUserList(users);
    showSuccess('数据已刷新');
  } catch (error) {
    showError('刷新数据失败');
  }
}

离线优先:打造极致用户体验

现代 Web 应用应该具备离线能力,让用户在网络不稳定时也能正常使用。

// 离线优先的数据同步器
class OfflineFirstSync {
  constructor() {
    this.dbManager = null;
    this.pendingSync = []; // 待同步的操作
    this.init();
  }
  
  async init() {
    // 初始化 IndexedDB
    this.dbManager = new DBManager('OfflineApp', 1);
    await this.dbManager.open();
    
    // 监听网络状态
    this.setupNetworkListener();
    
    // 尝试同步待处理的操作
    this.trySyncPending();
  }
  
  // 设置网络状态监听
  setupNetworkListener() {
    window.addEventListener('online', () => {
      console.log('网络已连接,开始同步数据...');
      this.trySyncPending();
    });
    
    window.addEventListener('offline', () => {
      console.log('网络已断开,进入离线模式');
      this.showOfflineIndicator();
    });
  }
  
  // 创建数据(离线优先)
  async createData(storeName, data) {
    // 先保存到本地数据库
    const localId = await this.dbManager.add(storeName, {
      ...data,
      _local: true,    // 标记为本地创建
      _synced: false,  // 未同步
      _createdAt: new Date()
    });
    
    // 添加到待同步队列
    this.pendingSync.push({
      type: 'create',
      storeName,
      data: { ...data, _localId: localId }
    });
    
    // 尝试立即同步
    await this.trySyncPending();
    
    return localId;
  }
  
  // 尝试同步待处理操作
  async trySyncPending() {
    if (!navigator.onLine || this.pendingSync.length === 0) {
      return;
    }
    
    console.log(`开始同步 ${this.pendingSync.length} 个操作`);
    
    const successes = [];
    const failures = [];
    
    for (const operation of [...this.pendingSync]) {
      try {
        await this.syncOperation(operation);
        successes.push(operation);
        
        // 从待同步队列中移除成功的操作
        const index = this.pendingSync.indexOf(operation);
        if (index > -1) {
          this.pendingSync.splice(index, 1);
        }
      } catch (error) {
        console.error('同步操作失败:', error);
        failures.push(operation);
      }
    }
    
    if (successes.length > 0) {
      console.log(`成功同步 ${successes.length} 个操作`);
      this.showSyncSuccess(successes.length);
    }
    
    if (failures.length > 0) {
      console.warn(`${failures.length} 个操作同步失败,将在下次重试`);
    }
  }
  
  // 同步单个操作
  async syncOperation(operation) {
    switch (operation.type) {
      case 'create':
        // 调用 API 创建数据
        const response = await fetch('/api/' + operation.storeName, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(operation.data)
        });
        
        if (!response.ok) {
          throw new Error(`创建失败: ${response.status}`);
        }
        
        const result = await response.json();
        
        // 更新本地数据,标记为已同步
        // 这里可以根据需要更新本地记录的ID等
        console.log('数据同步成功:', result);
        break;
        
      default:
        console.warn('未知的操作类型:', operation.type);
    }
  }
  
  // 显示离线指示器
  showOfflineIndicator() {
    // 在实际应用中,可以显示一个离线提示条
    const indicator = document.createElement('div');
    indicator.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      background: #ff6b6b;
      color: white;
      text-align: center;
      padding: 10px;
      z-index: 1000;
    `;
    indicator.textContent = '当前处于离线模式,部分功能可能受限';
    indicator.id = 'offline-indicator';
    
    document.body.appendChild(indicator);
  }
  
  // 显示同步成功提示
  showSyncSuccess(count) {
    const indicator = document.getElementById('offline-indicator');
    if (indicator) {
      indicator.remove();
    }
    
    // 显示同步成功提示(可以替换为更优雅的通知)
    console.log(`成功同步 ${count} 条数据`);
  }
  
  // 获取数据(离线优先)
  async getData(storeName, useLocalFirst = true) {
    if (useLocalFirst) {
      // 先返回本地数据
      const localData = await this.dbManager.getAll(storeName);
      
      // 同时在后台尝试获取最新数据
      this.fetchLatestData(storeName);
      
      return localData;
    } else {
      // 直接获取最新数据
      return await this.fetchLatestData(storeName);
    }
  }
  
  // 获取最新数据
  async fetchLatestData(storeName) {
    if (!navigator.onLine) {
      throw new Error('网络不可用');
    }
    
    try {
      const response = await fetch(`/api/${storeName}`);
      if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
      
      const data = await response.json();
      
      // 更新本地数据库
      // 这里需要根据具体业务逻辑实现数据合并
      console.log('获取到最新数据:', data);
      
      return data;
    } catch (error) {
      console.error('获取最新数据失败:', error);
      throw error;
    }
  }
}

// 使用示例
const offlineSync = new OfflineFirstSync();

// 在离线状态下创建用户
async function createUserOffline(userData) {
  try {
    const localId = await offlineSync.createData('users', userData);
    console.log('用户已创建(本地):', localId);
    showSuccess('用户已保存,将在网络恢复后同步');
  } catch (error) {
    console.error('创建用户失败:', error);
    showError('保存用户失败');
  }
}

性能优化与最佳实践

掌握了基础用法,再来看看一些提升性能的实用技巧。

// 防抖请求,避免频繁调用接口
function createDebouncedFetcher(delay = 500) {
  let timeoutId;
  
  return async function debouncedFetch(url, options) {
    // 清除之前的定时器
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    
    // 设置新的定时器
    return new Promise((resolve, reject) => {
      timeoutId = setTimeout(async () => {
        try {
          const response = await fetch(url, options);
          const data = await response.json();
          resolve(data);
        } catch (error) {
          reject(error);
        }
      }, delay);
    });
  };
}

// 使用防抖的搜索功能
const debouncedSearch = createDebouncedFetcher(300);

document.getElementById('searchInput').addEventListener('input', async (event) => {
  const query = event.target.value.trim();
  
  if (query.length < 2) {
    // 清空搜索结果
    clearSearchResults();
    return;
  }
  
  try {
    const results = await debouncedSearch(`/api/search?q=${encodeURIComponent(query)}`);
    displaySearchResults(results);
  } catch (error) {
    console.error('搜索失败:', error);
    // 可以显示本地缓存的结果或错误提示
  }
});

// 批量操作优化
async function batchOperations(operations, batchSize = 5) {
  const results = [];
  
  for (let i = 0; i < operations.length; i += batchSize) {
    const batch = operations.slice(i, i + batchSize);
    
    // 并行执行批次内的操作
    const batchResults = await Promise.allSettled(
      batch.map(op => executeOperation(op))
    );
    
    results.push(...batchResults);
    
    // 可选:批次间延迟,避免对服务器造成太大压力
    if (i + batchSize < operations.length) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }
  
  return results;
}

// 数据压缩,减少存储空间
function compressData(data) {
  // 简单的数据压缩示例
  const compressed = {
    // 移除空值
    ...Object.fromEntries(
      Object.entries(data).filter(([_, value]) => 
        value !== null && value !== undefined && value !== ''
      )
    ),
    // 添加压缩标记
    _compressed: true
  };
  
  return compressed;
}

// 数据解压缩
function decompressData(compressedData) {
  const { _compressed, ...data } = compressedData;
  return data;
}

// 使用压缩存储
function saveCompressedData(key, data) {
  const compressed = compressData(data);
  localStorage.setItem(key, JSON.stringify(compressed));
}

function loadCompressedData(key) {
  const stored = localStorage.getItem(key);
  if (stored) {
    const compressed = JSON.parse(stored);
    return decompressData(compressed);
  }
  return null;
}

错误处理与监控

健壮的应用离不开完善的错误处理。

// 增强的错误处理包装器
function createRobustFetcher(options = {}) {
  const {
    maxRetries = 3,
    retryDelay = 1000,
    timeout = 10000
  } = options;
  
  return async function robustFetch(url, fetchOptions = {}) {
    let lastError;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        // 创建超时控制器
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);
        
        const response = await fetch(url, {
          ...fetchOptions,
          signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        return await response.json();
        
      } catch (error) {
        lastError = error;
        
        console.warn(`请求失败 (尝试 ${attempt}/${maxRetries}):`, error);
        
        if (attempt < maxRetries) {
          // 指数退避延迟
          const delay = retryDelay * Math.pow(2, attempt - 1);
          console.log(`等待 ${delay}ms 后重试...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }
    
    // 所有重试都失败了
    throw new Error(`请求失败,已重试 ${maxRetries} 次: ${lastError.message}`);
  };
}

// 使用增强的请求器
const robustFetch = createRobustFetcher({
  maxRetries: 3,
  retryDelay: 1000,
  timeout: 15000
});

// 数据健康检查
class DataHealthChecker {
  static checkLocalStorage() {
    const issues = [];
    
    try {
      // 测试写入和读取
      const testKey = '__health_check__';
      const testValue = { timestamp: Date.now() };
      
      localStorage.setItem(testKey, JSON.stringify(testValue));
      const retrieved = JSON.parse(localStorage.getItem(testKey));
      localStorage.removeItem(testKey);
      
      if (!retrieved || retrieved.timestamp !== testValue.timestamp) {
        issues.push('localStorage 数据完整性检查失败');
      }
    } catch (error) {
      issues.push(`localStorage 不可用: ${error.message}`);
    }
    
    return issues;
  }
  
  static checkIndexedDB() {
    return new Promise((resolve) => {
      const issues = [];
      
      const request = indexedDB.open('health_check', 1);
      request.onerror = () => {
        issues.push('IndexedDB 无法打开');
        resolve(issues);
      };
      
      request.onsuccess = () => {
        const db = request.result;
        db.close();
        
        // 清理测试数据库
        indexedDB.deleteDatabase('health_check');
        resolve(issues);
      };
      
      request.onblocked = () => {
        issues.push('IndexedDB 被阻塞');
        resolve(issues);
      };
    });
  }
  
  static async runAllChecks() {
    const localStorageIssues = this.checkLocalStorage();
    const indexedDBIssues = await this.checkIndexedDB();
    
    const allIssues = [...localStorageIssues, ...indexedDBIssues];
    
    if (allIssues.length === 0) {
      console.log('✅ 所有存储系统正常');
    } else {
      console.warn('❌ 存储系统问题:', allIssues);
    }
    
    return allIssues;
  }
}

// 定期运行健康检查
setInterval(async () => {
  await DataHealthChecker.runAllChecks();
}, 5 * 60 * 1000); // 每5分钟检查一次

总结

通过今天的学习,相信你已经掌握了:

✅ Fetch API 的现代用法和错误处理 ✅ 三种本地存储方案的适用场景 ✅ 如何构建智能缓存系统提升性能 ✅ 离线优先的设计思路 ✅ 各种性能优化和监控技巧

数据交互不再是简单的"请求-显示",而是要考虑缓存、离线、同步、性能等方方面面。一个好的数据层设计,能让你的应用用户体验提升好几个档次。

从原型到类:JavaScript面向对象编程的终极进化指南

2025年10月27日 07:31

你是不是也曾经被JavaScript的原型链绕得头晕眼花?每次看到__proto__prototype就感觉在看天书?别担心,这几乎是每个前端开发者都会经历的阶段。

今天我要带你彻底搞懂JavaScript面向对象编程的进化之路。从令人困惑的原型到优雅的class语法,再到实际项目中的设计模式应用,读完本文,你不仅能理解JS面向对象的本质,还能写出更优雅、更易维护的代码。

原型时代:JavaScript的"上古时期"

在ES6之前,JavaScript面向对象编程全靠原型链。虽然语法看起来有点奇怪,但理解它对我们掌握JS面向对象至关重要。

让我们先看一个最简单的原型继承例子:

// 构造函数 - 相当于其他语言中的类
function Animal(name) {
  this.name = name;
}

// 通过原型添加方法
Animal.prototype.speak = function() {
  console.log(this.name + ' makes a noise.');
}

// 创建实例
var dog = new Animal('Dog');
dog.speak(); // 输出: Dog makes a noise.

这里发生了什么?我们用Animal函数创建了一个"类",通过prototype给所有实例共享方法。这样创建的实例都能调用speak方法。

再来看看继承怎么实现:

// 子类构造函数
function Dog(name) {
  // 调用父类构造函数
  Animal.call(this, name);
}

// 设置原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 添加子类特有方法
Dog.prototype.speak = function() {
  console.log(this.name + ' barks.');
}

var myDog = new Dog('Rex');
myDog.speak(); // 输出: Rex barks.

是不是感觉有点繁琐?这就是为什么ES6要引入class语法 - 让面向对象编程变得更直观。

Class时代:ES6带来的语法糖

ES6的class并不是引入了新的面向对象继承模型,而是基于原型的语法糖。但不得不说,这个糖真的很甜!

同样的功能,用class怎么写:

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // 调用父类构造函数
  }
  
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const myDog = new Dog('Rex');
myDog.speak(); // 输出: Rex barks.

代码是不是清晰多了?class语法让我们能够用更接近传统面向对象语言的方式编写代码,大大提高了可读性。

但要注意,class本质上还是基于原型的。我们可以验证一下:

console.log(typeof Animal); // 输出: function
console.log(Animal.prototype.speak); // 输出: [Function: speak]

看到没?class其实就是构造函数的语法糖,方法还是在prototype上。

封装与私有字段:保护你的数据

面向对象三大特性之一的封装,在JavaScript中经历了很多变化。从最初的命名约定到现在的真正私有字段,让我们来看看进化历程。

早期的做法是用下划线约定:

class BankAccount {
  constructor(balance) {
    this._balance = balance; // 下划线表示"私有"
  }
  
  getBalance() {
    return this._balance;
  }
}

但这只是约定,实际上还是可以访问:

const account = new BankAccount(100);
console.log(account._balance); // 还是能访问到,不安全

ES6之后,我们可以用Symbol实现真正的私有:

const _balance = Symbol('balance');

class BankAccount {
  constructor(balance) {
    this[_balance] = balance;
  }
  
  getBalance() {
    return this[_balance];
  }
}

const account = new BankAccount(100);
console.log(account[_balance]); // 理论上拿不到,除非拿到Symbol引用

最新的ES提案提供了真正的私有字段语法:

class BankAccount {
  #balance; // 私有字段
  
  constructor(balance) {
    this.#balance = balance;
  }
  
  getBalance() {
    return this.#balance;
  }
  
  // 静态私有字段
  static #bankName = 'MyBank';
}

const account = new BankAccount(100);
console.log(account.#balance); // 语法错误:私有字段不能在类外访问

现在我们的数据真正安全了!

设计模式实战:用OOP解决复杂问题

理解了基础语法,让我们看看在实际项目中如何运用面向对象思想和设计模式。

单例模式:全局状态管理

单例模式确保一个类只有一个实例,这在管理全局状态时特别有用。

class AppConfig {
  static instance = null;
  
  constructor() {
    if (AppConfig.instance) {
      return AppConfig.instance;
    }
    
    this.theme = 'light';
    this.language = 'zh-CN';
    this.apiBaseUrl = 'https://api.example.com';
    
    AppConfig.instance = this;
  }
  
  static getInstance() {
    if (!AppConfig.instance) {
      AppConfig.instance = new AppConfig();
    }
    return AppConfig.instance;
  }
  
  setTheme(theme) {
    this.theme = theme;
  }
}

// 使用
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();

console.log(config1 === config2); // 输出: true - 确实是同一个实例

观察者模式:实现事件驱动架构

观察者模式在UI开发中无处不在,让我们自己实现一个简单的事件系统:

class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  // 订阅事件
  on(eventName, listener) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(listener);
    
    // 返回取消订阅的函数
    return () => {
      this.off(eventName, listener);
    };
  }
  
  // 取消订阅
  off(eventName, listener) {
    if (!this.events[eventName]) return;
    
    this.events[eventName] = this.events[eventName].filter(
      l => l !== listener
    );
  }
  
  // 发布事件
  emit(eventName, data) {
    if (!this.events[eventName]) return;
    
    this.events[eventName].forEach(listener => {
      try {
        listener(data);
      } catch (error) {
        console.error(`Error in event listener for ${eventName}:`, error);
      }
    });
  }
}

// 使用示例
class User extends EventEmitter {
  constructor(name) {
    super();
    this.name = name;
  }
  
  login() {
    console.log(`${this.name} logged in`);
    this.emit('login', { user: this.name, time: new Date() });
  }
}

const user = new User('John');

// 订阅登录事件
const unsubscribe = user.on('login', (data) => {
  console.log('登录事件触发:', data);
});

user.login();
// 输出:
// John logged in
// 登录事件触发: { user: 'John', time: ... }

// 取消订阅
unsubscribe();

工厂模式:灵活的对象创建

当创建逻辑比较复杂,或者需要根据不同条件创建不同对象时,工厂模式就派上用场了。

class Notification {
  constructor(message) {
    this.message = message;
  }
  
  send() {
    throw new Error('send method must be implemented');
  }
}

class EmailNotification extends Notification {
  send() {
    console.log(`Sending email: ${this.message}`);
    // 实际的邮件发送逻辑
    return true;
  }
}

class SMSNotification extends Notification {
  send() {
    console.log(`Sending SMS: ${this.message}`);
    // 实际的短信发送逻辑
    return true;
  }
}

class PushNotification extends Notification {
  send() {
    console.log(`Sending push: ${this.message}`);
    // 实际的推送逻辑
    return true;
  }
}

class NotificationFactory {
  static createNotification(type, message) {
    switch (type) {
      case 'email':
        return new EmailNotification(message);
      case 'sms':
        return new SMSNotification(message);
      case 'push':
        return new PushNotification(message);
      default:
        throw new Error(`Unknown notification type: ${type}`);
    }
  }
}

// 使用工厂
const email = NotificationFactory.createNotification('email', 'Hello!');
email.send(); // 输出: Sending email: Hello!

const sms = NotificationFactory.createNotification('sms', 'Your code is 1234');
sms.send(); // 输出: Sending SMS: Your code is 1234

高级技巧:混入和组合

JavaScript的灵活性让我们可以实现一些在其他语言中比较困难的功能,比如混入模式。

// 混入函数
const CanSpeak = (Base) => class extends Base {
  speak() {
    console.log(`${this.name} speaks`);
  }
};

const CanWalk = (Base) => class extends Base {
  walk() {
    console.log(`${this.name} walks`);
  }
};

const CanSwim = (Base) => class extends Base {
  swim() {
    console.log(`${this.name} swims`);
  }
};

// 组合不同的能力
class Person {
  constructor(name) {
    this.name = name;
  }
}

// 创建一个会说话和走路的人
class SpeakingWalkingPerson extends CanWalk(CanSpeak(Person)) {}

// 创建一个会所有技能的人
class SuperPerson extends CanSwim(CanWalk(CanSpeak(Person))) {}

const john = new SpeakingWalkingPerson('John');
john.speak(); // John speaks
john.walk();  // John walks

const superman = new SuperPerson('Superman');
superman.speak(); // Superman speaks
superman.walk();  // Superman walks  
superman.swim();  // Superman swims

这种组合的方式让我们可以像搭积木一样构建对象的功能,非常灵活!

性能优化:原型 vs Class

很多人会问,class语法会不会影响性能?让我们实际测试一下:

// 原型方式
function ProtoAnimal(name) {
  this.name = name;
}
ProtoAnimal.prototype.speak = function() {
  return this.name + ' speaks';
};

// Class方式
class ClassAnimal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return this.name + ' speaks';
  }
}

// 性能测试
console.time('Proto创建实例');
for (let i = 0; i < 100000; i++) {
  new ProtoAnimal('test');
}
console.timeEnd('Proto创建实例');

console.time('Class创建实例');
for (let i = 0; i < 100000; i++) {
  new ClassAnimal('test');
}
console.timeEnd('Class创建实例');

在现代JavaScript引擎中,两者的性能差异可以忽略不计。class语法经过优化,在大多数情况下甚至可能略快一些。

实战案例:构建一个简单的UI组件库

让我们用今天学到的知识,构建一个简单的UI组件库:

// 基础组件类
class Component {
  constructor(element) {
    this.element = element;
    this.init();
  }
  
  init() {
    // 初始化逻辑
    this.bindEvents();
  }
  
  bindEvents() {
    // 绑定事件 - 由子类实现
  }
  
  show() {
    this.element.style.display = 'block';
  }
  
  hide() {
    this.element.style.display = 'none';
  }
  
  // 静态方法用于创建组件
  static create(selector) {
    const element = document.querySelector(selector);
    return new this(element);
  }
}

// 按钮组件
class Button extends Component {
  bindEvents() {
    this.element.addEventListener('click', () => {
      this.onClick();
    });
  }
  
  onClick() {
    console.log('Button clicked!');
    this.emit('click'); // 如果继承了EventEmitter
  }
  
  setText(text) {
    this.element.textContent = text;
  }
}

// 模态框组件
class Modal extends Component {
  bindEvents() {
    // 关闭按钮事件
    const closeBtn = this.element.querySelector('.close');
    if (closeBtn) {
      closeBtn.addEventListener('click', () => {
        this.hide();
      });
    }
  }
  
  setContent(content) {
    const contentEl = this.element.querySelector('.modal-content');
    if (contentEl) {
      contentEl.innerHTML = content;
    }
  }
}

// 使用
const myButton = Button.create('#myButton');
const myModal = Modal.create('#myModal');

myButton.setText('点击我');
myButton.on('click', () => {
  myModal.setContent('<h2>Hello Modal!</h2>');
  myModal.show();
});

常见陷阱与最佳实践

在JavaScript面向对象编程中,有一些常见的坑需要注意:

1. 绑定this的问题

class MyClass {
  constructor() {
    this.value = 42;
  }
  
  // 错误:这样会丢失this
  printValue() {
    console.log(this.value);
  }
}

const instance = new MyClass();
const func = instance.printValue;
func(); // TypeError: Cannot read property 'value' of undefined

// 解决方法1:在构造函数中绑定
class MyClassFixed1 {
  constructor() {
    this.value = 42;
    this.printValue = this.printValue.bind(this);
  }
  
  printValue() {
    console.log(this.value);
  }
}

// 解决方法2:使用箭头函数
class MyClassFixed2 {
  constructor() {
    this.value = 42;
  }
  
  printValue = () => {
    console.log(this.value);
  }
}

2. 继承中的super调用

class Parent {
  constructor(name) {
    this.name = name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    // 必须首先调用super!
    super(name);
    this.age = age;
  }
}

3. 私有字段的兼容性

// 在生产环境中,如果需要支持旧浏览器,可以考虑使用Babel转译
// 或者使用传统的闭包方式实现私有性

function createPrivateCounter() {
  let count = 0; // 真正的私有变量
  
  return {
    increment() {
      count++;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createPrivateCounter();
console.log(counter.increment()); // 1
console.log(counter.count); // undefined - 无法直接访问

面向未来的JavaScript OOP

JavaScript的面向对象编程还在不断发展,一些新的特性值得关注:

1. 装饰器提案

// 目前还是Stage 3提案,但已经在很多项目中使用
@sealed
class Person {
  @readonly
  name = 'John';
  
  @deprecate
  oldMethod() {
    // ...
  }
}

2. 更强大的元编程

// 使用Proxy实现高级功能
const createValidator = (target) => {
  return new Proxy(target, {
    set(obj, prop, value) {
      if (prop === 'age' && (value < 0 || value > 150)) {
        throw new Error('Invalid age');
      }
      obj[prop] = value;
      return true;
    }
  });
};

class Person {
  constructor() {
    return createValidator(this);
  }
}

const person = new Person();
person.age = 25; // 正常
person.age = 200; // 抛出错误

总结

JavaScript的面向对象编程经历了一场精彩的进化:从令人困惑的原型链,到优雅的class语法,再到各种设计模式的实践应用。

记住这些关键点:

  • class是语法糖,理解原型链仍然很重要
  • 私有字段让封装更安全
  • 设计模式能解决特定类型的复杂问题
  • 组合优于继承在很多场景下更灵活

面向对象不是银弹,但在构建复杂的前端应用时,良好的OOP设计能显著提高代码的可维护性和可扩展性。

你现在对JavaScript面向对象编程的理解到什么程度了?在实际项目中遇到过哪些OOP的挑战?欢迎在评论区分享你的经验和问题!

ES6+革命:8大特性让你的JavaScript代码质量翻倍

2025年10月24日 07:34

最近review代码的时候,看到一些还在用var声明变量、用function写满屏回调的代码,我真的有点头疼。

你是不是也遇到过这样的困扰:代码写着写着就乱了,变量莫名其妙被修改,回调嵌套到怀疑人生?其实这些问题,ES6+早就给出了优雅的解决方案。

今天我就带你彻底告别老旧的JS写法,用8个核心特性让你的代码质量直接翻倍!每个特性我都会配上详细注释的代码示例,保证你能立刻上手。

let和const:告别变量提升的噩梦

还记得用var时那些诡异的现象吗?变量莫名其妙被提升,循环计数器失效... let和const就是来拯救你的。

// 老写法 - 充满陷阱
var count = 10;
if (true) {
  var count = 20; // 啊哦,外面的count也被修改了!
}
console.log(count); // 输出20,意外吧?

// 新写法 - 安全可靠
let score = 100;
if (true) {
  let score = 200; // 块级作用域,互不影响
  console.log(score); // 输出200
}
console.log(score); // 输出100,完美!

// const用于常量,一旦赋值不能修改
const PI = 3.14159;
// PI = 3.14; // 这行会报错,保护你的常量不被误改

const user = { name: '小明' };
user.name = '小红'; // 这是可以的,修改对象属性
// user = {}; // 这不行,不能重新赋值

看到区别了吗?let和const让变量的作用域更加明确,大大减少了潜在的bug。

箭头函数:简化回调,绑定this

以前写回调函数最头疼的就是this指向问题,箭头函数让这一切变得简单。

// 老写法 - this指向让人困惑
function Counter() {
  this.count = 0;
  setInterval(function() {
    this.count++; // 这里的this指向window,不是Counter实例!
    console.log(this.count);
  }, 1000);
}

// 新写法 - 箭头函数自动绑定外部this
function Counter() {
  this.count = 0;
  setInterval(() => {
    this.count++; // 这里的this正确指向Counter实例
    console.log(this.count);
  }, 1000);
}

// 语法简化对比
const numbers = [1, 2, 3, 4, 5];

// 老写法
const squares = numbers.map(function(num) {
  return num * num;
});

// 新写法 - 简洁明了
const squares = numbers.map(num => num * num);

// 多参数需要括号
const sum = numbers.reduce((total, num) => total + num, 0);

// 多行语句需要大括号
const evenSquares = numbers.map(num => {
  if (num % 2 === 0) {
    return num * num;
  }
  return null;
});

箭头函数不仅让代码更简洁,还彻底解决了this绑定的困扰。

模板字符串:告别字符串拼接地狱

还记得用加号拼接字符串的痛苦吗?模板字符串让你重获新生。

// 老写法 - 眼花缭乱
const user = { name: '李雷', age: 25 };
const message = '你好,' + user.name + '!你今年' + user.age + '岁了。';
console.log(message);

// 新写法 - 清晰直观
const message = `你好,${user.name}!你今年${user.age}岁了。`;
console.log(message);

// 支持多行字符串,太方便了!
const emailTemplate = `
尊敬的${user.name}:

  感谢您使用我们的服务。
  您的账户信息:
  姓名:${user.name}
  年龄:${user.age}

祝好!
团队敬上
`;

// 甚至在${}中可以做运算
const calculation = `5 + 3 = ${5 + 3}`; // "5 + 3 = 8"

// 调用函数
function greet(name) {
  return `Hello, ${name}!`;
}
const greeting = `${greet('韩梅梅')} 欢迎回来!`;

模板字符串让拼接字符串变得像写正常文本一样自然。

解构赋值:优雅的数据提取

从对象和数组中提取数据再也不需要写一堆临时变量了。

// 对象解构 - 从user对象中提取name和age
const user = { 
  name: '王小明', 
  age: 28, 
  email: 'wang@example.com',
  address: {
    city: '北京',
    district: '朝阳区'
  }
};

// 老写法 - 繁琐重复
const name = user.name;
const age = user.age;
const email = user.email;

// 新写法 - 一行搞定
const { name, age, email } = user;
console.log(name, age, email); // 王小明 28 wang@example.com

// 重命名变量
const { name: userName, age: userAge } = user;

// 嵌套解构
const { address: { city, district } } = user;
console.log(city, district); // 北京 朝阳区

// 数组解构
const colors = ['红色', '绿色', '蓝色'];

// 老写法
const first = colors[0];
const second = colors[1];

// 新写法
const [first, second, third] = colors;
console.log(first, second); // 红色 绿色

// 交换变量 - 不再需要临时变量
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

// 函数参数解构
function printUser({ name, age }) {
  console.log(`${name}今年${age}岁`);
}
printUser(user); // 王小明今年28岁

解构赋值让代码更加简洁,意图更加明确。

默认参数和Rest参数:函数用起来更顺手

给函数参数设置默认值,处理不定数量参数,现在都有优雅的解决方案。

// 默认参数 - 告别||操作符
// 老写法
function greet(name) {
  name = name || '访客';
  return `你好,${name}!`;
}

// 新写法
function greet(name = '访客') {
  return `你好,${name}!`;
}

console.log(greet()); // 你好,访客!
console.log(greet('李雷')); // 你好,李雷!

// 默认参数可以是表达式
function createUser(name, age = 18, registered = Date.now()) {
  return { name, age, registered };
}

// Rest参数 - 处理不定数量参数
// 老写法 - 使用arguments
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

// 新写法 - 使用Rest参数
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// Rest参数必须是最后一个参数
function introduce(name, age, ...hobbies) {
  console.log(`${name}今年${age}岁,爱好:${hobbies.join('、')}`);
}
introduce('韩梅梅', 25, '读书', '游泳', '摄影'); // 韩梅梅今年25岁,爱好:读书、游泳、摄影

// 与解构结合使用
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]

这些特性让函数定义更加灵活和健壮。

扩展运算符:数组和对象的瑞士军刀

扩展运算符就像一把万能钥匙,能解决很多日常开发中的常见问题。

// 数组操作
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 数组合并 - 老写法要用concat
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// 数组复制
const arrCopy = [...arr1];
console.log(arrCopy); // [1, 2, 3]

// 在特定位置插入元素
const newArr = [0, ...arr1, 3.5, ...arr2, 7];
console.log(newArr); // [0, 1, 2, 3, 3.5, 4, 5, 6, 7]

// 对象操作
const user = { name: '张三', age: 30 };
const preferences = { theme: 'dark', language: 'zh-CN' };

// 对象合并
const userWithPrefs = { ...user, ...preferences };
console.log(userWithPrefs); // {name: "张三", age: 30, theme: "dark", language: "zh-CN"}

// 对象复制与更新
const updatedUser = { ...user, age: 31 };
console.log(updatedUser); // {name: "张三", age: 31}

// 函数调用时展开数组
const numbers = [1, 2, 3, 4, 5];
console.log(Math.max(...numbers)); // 5,相当于Math.max(1, 2, 3, 4, 5)

// 字符串转数组
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']

扩展运算符让数据处理变得异常简单和直观。

Promise和async/await:告别回调地狱

这是最重要的改进之一,让异步代码写得像同步代码一样清晰。

// 老写法 - 回调地狱
function fetchData(callback) {
  setTimeout(() => {
    console.log('数据获取完成');
    callback('数据内容');
  }, 1000);
}

fetchData(function(data) {
  processData(data, function(result) {
    saveData(result, function() {
      console.log('全部完成');
    });
  });
});

// Promise写法
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('数据获取完成');
      resolve('数据内容');
      // 如果出错:reject(new Error('获取失败'));
    }, 1000);
  });
}

fetchData()
  .then(data => {
    console.log('处理数据:', data);
    return processData(data);
  })
  .then(result => {
    console.log('保存数据:', result);
    return saveData(result);
  })
  .then(() => {
    console.log('全部完成');
  })
  .catch(error => {
    console.error('出错了:', error);
  });

// async/await - 终极解决方案
async function main() {
  try {
    const data = await fetchData();
    console.log('处理数据:', data);
    
    const result = await processData(data);
    console.log('保存数据:', result);
    
    await saveData(result);
    console.log('全部完成');
  } catch (error) {
    console.error('出错了:', error);
  }
}

main();

// 实际示例:顺序执行多个异步操作
async function getUserData(userId) {
  try {
    // 等待用户信息
    const userInfo = await fetch(`/api/users/${userId}`);
    const user = await userInfo.json();
    
    // 等待用户订单
    const ordersInfo = await fetch(`/api/users/${userId}/orders`);
    const orders = await ordersInfo.json();
    
    // 等待用户地址
    const addressInfo = await fetch(`/api/users/${userId}/address`);
    const address = await addressInfo.json();
    
    return {
      user,
      orders,
      address
    };
  } catch (error) {
    console.error('获取用户数据失败:', error);
    throw error;
  }
}

async/await让异步代码的可读性达到了全新高度。

模块化:代码组织的艺术

ES6模块让前端工程化成为可能,告别全局变量污染。

// math.js - 导出模块
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// 默认导出
export default class Calculator {
  constructor() {
    this.result = 0;
  }
  
  clear() {
    this.result = 0;
  }
}

// app.js - 导入模块
import Calculator, { PI, add, multiply } from './math.js';

// 使用导入的功能
const calc = new Calculator();
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20

// 批量导入
import * as math from './math.js';
console.log(math.PI); // 3.14159
console.log(math.add(1, 2)); // 3

// 动态导入 - 按需加载
async function loadModule() {
  const module = await import('./math.js');
  console.log(module.PI); // 3.14159
}

// 在实际项目中的使用
// utils/request.js
export async function get(url) {
  const response = await fetch(url);
  return response.json();
}

export async function post(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  return response.json();
}

// components/UserList.js
import { get } from '../utils/request.js';

export async function loadUsers() {
  return await get('/api/users');
}

// app.js
import { loadUsers } from './components/UserList.js';

async function init() {
  const users = await loadUsers();
  console.log(users);
}

模块化让代码组织更加清晰,依赖关系更加明确。

实际项目重构示例

让我们来看一个真实的重构案例,感受ES6+带来的巨大变化。

// 老写法 - ES5时代
var userService = (function() {
  var apiUrl = 'https://api.example.com';
  
  function UserService() {
    this.users = [];
  }
  
  UserService.prototype.fetchUsers = function(callback) {
    var self = this;
    $.ajax({
      url: apiUrl + '/users',
      method: 'GET',
      success: function(data) {
        self.users = data;
        callback(null, data);
      },
      error: function(err) {
        callback(err, null);
      }
    });
  };
  
  UserService.prototype.getUserById = function(id, callback) {
    var foundUser = null;
    for (var i = 0; i < this.users.length; i++) {
      if (this.users[i].id === id) {
        foundUser = this.users[i];
        break;
      }
    }
    callback(null, foundUser);
  };
  
  return UserService;
})();

// 新写法 - ES6+现代化
const API_URL = 'https://api.example.com';

class UserService {
  constructor() {
    this.users = [];
  }
  
  async fetchUsers() {
    try {
      const response = await fetch(`${API_URL}/users`);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      this.users = await response.json();
      return this.users;
    } catch (error) {
      console.error('获取用户列表失败:', error);
      throw error;
    }
  }
  
  getUserById(id) {
    return this.users.find(user => user.id === id);
  }
  
  // 使用Rest参数和箭头函数
  getUsersByIds(...ids) {
    return this.users.filter(user => ids.includes(user.id));
  }
  
  // 使用解构和模板字符串
  createUser(userData) {
    const { name, email, age } = userData;
    const newUser = {
      id: Date.now().toString(),
      name,
      email,
      age,
      createdAt: new Date().toISOString()
    };
    
    this.users = [...this.users, newUser];
    console.log(`用户 ${name} 创建成功`);
    return newUser;
  }
}

// 使用示例
async function main() {
  const userService = new UserService();
  
  try {
    // 获取用户列表
    const users = await userService.fetchUsers();
    console.log('用户列表:', users);
    
    // 根据ID查找用户
    const user = userService.getUserById('123');
    console.log('找到用户:', user);
    
    // 批量查找用户
    const multipleUsers = userService.getUsersByIds('123', '456', '789');
    console.log('多个用户:', multipleUsers);
    
    // 创建新用户
    const newUser = userService.createUser({
      name: '李四',
      email: 'lisi@example.com',
      age: 28
    });
    console.log('新用户:', newUser);
    
  } catch (error) {
    console.error('操作失败:', error);
  }
}

// 立即执行函数
main();

看到差别了吗?新代码不仅更简洁,而且更易读、更易维护。

写在最后

ES6+的这些特性不是炫技,而是真正能提升代码质量和开发效率的实用工具。从今天开始,试着在你的项目中用起来:

  1. 从小处开始:先从let/const和箭头函数用起
  2. 渐进式改进:每次修改代码时,顺便把老语法升级
  3. 团队约定:和团队成员制定统一的代码规范

记住,好的代码不是写出来给机器看的,而是写出来给人看的。ES6+的特性让我们的代码更加表达意图,减少意外,提升可维护性。

你现在在用哪些ES6+特性?有没有在升级过程中遇到过什么问题?欢迎在评论区分享你的经验和困惑!

升级你的JS技能,从现在开始!

❌
❌