普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月9日首页

JavaScript模块化深度解析:从CommonJS到ES Modules的演进之路

作者 bluceli
2026年3月9日 15:34

引言

JavaScript模块化是现代前端开发的基石,它让代码组织更加清晰、可维护性更强。从早期的全局变量污染,到CommonJS、AMD、CMD,再到如今的ES Modules,JavaScript模块化经历了漫长的演进过程。本文将深入探讨JavaScript模块化的发展历程、核心概念和最佳实践,帮助你全面掌握这个前端开发必备技能。

模块化的发展历程

1. 早期JavaScript的困境

在JavaScript早期,没有模块化概念,所有代码都在全局作用域中执行。

// 早期的问题代码
// utils.js
var name = '工具函数';

function formatDate() {
  // 格式化日期逻辑
}

// app.js
var name = '应用代码'; // 变量名冲突!

function formatDate() {
  // 与utils.js中的函数冲突
}

这种开发方式存在严重问题:

  • 全局变量污染
  • 命名冲突
  • 依赖关系混乱
  • 无法按需加载

2. 命名空间模式

为了解决全局污染问题,开发者开始使用命名空间。

// 使用对象作为命名空间
var MyApp = MyApp || {};

MyApp.utils = {
  formatDate: function(date) {
    return new Date(date).toLocaleDateString();
  },
  
  debounce: function(func, delay) {
    var timer;
    return function() {
      clearTimeout(timer);
      timer = setTimeout(func, delay);
    };
  }
};

MyApp.services = {
  userService: {
    getUser: function(id) {
      // 获取用户逻辑
    }
  }
};

// 使用
MyApp.utils.formatDate('2024-01-15');

虽然命名空间缓解了问题,但仍然存在依赖管理困难等问题。

CommonJS规范

3. CommonJS基础

CommonJS是Node.js采用的模块化规范,使用require和module.exports。

// math.js - 定义模块
function add(a, b) {
  return a + b;
}

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

// 导出多个函数
module.exports = {
  add: add,
  multiply: multiply
};

// 或者使用exports对象
exports.add = add;
exports.multiply = multiply;
// app.js - 使用模块
var math = require('./math');

console.log(math.add(2, 3));        // 5
console.log(math.multiply(4, 5));    // 20

4. CommonJS的特点

// CommonJS的核心特性

// 1. 同步加载
var fs = require('fs'); // 阻塞等待模块加载完成

// 2. 运行时加载
var module = require('./module'); // 在运行时动态加载

// 3. 值的拷贝
// counter.js
var count = 0;
module.exports = {
  count: count,
  increment: function() {
    count++;
  }
};

// app.js
var counter = require('./counter');
console.log(counter.count); // 0
counter.increment();
console.log(counter.count); // 仍然是0,因为是值的拷贝

// 4. 单例模式
// 多次require同一个模块,只加载一次
var module1 = require('./module');
var module2 = require('./module');
console.log(module1 === module2); // true

5. CommonJS的模块缓存机制

// 模块缓存示例
// cache.js
console.log('模块被加载');
module.exports = {
  data: 'cached data'
};

// app.js
require('./cache'); // 输出: 模块被加载
require('./cache'); // 不再输出,使用缓存
require('./cache'); // 不再输出,使用缓存

// 清除缓存
delete require.cache[require.resolve('./cache')];
require('./cache'); // 再次输出: 模块被加载

AMD规范

6. RequireJS与AMD

AMD(Asynchronous Module Definition)是异步模块定义规范,RequireJS是其最著名的实现。

// 定义模块
define(['dependency1', 'dependency2'], function(dep1, dep2) {
  // 模块代码
  var privateVar = 'private';
  
  function publicFunction() {
    return dep1.doSomething() + dep2.doSomething();
  }
  
  // 返回公共API
  return {
    publicFunction: publicFunction
  };
});

// 简单模块定义
define(function() {
  return {
    version: '1.0.0'
  };
});
// 使用模块
require(['math', 'utils'], function(math, utils) {
  var result = math.add(10, 20);
  console.log(utils.formatDate(result));
});

7. RequireJS配置

<!-- index.html -->
<script src="require.js" data-main="app"></script>
// require.config.js
require.config({
  // 基础路径
  baseUrl: 'js/lib',
  
  // 路径映射
  paths: {
    'jquery': 'jquery.min',
    'underscore': 'underscore.min',
    'backbone': 'backbone.min'
  },
  
  // 模块依赖
  shim: {
    'backbone': {
      deps: ['underscore', 'jquery'],
      exports: 'Backbone'
    }
  }
});

// 加载主模块
require(['app/main'], function(main) {
  main.init();
});

ES Modules(ESM)

8. ES Modules基础语法

ES Modules是JavaScript官方的模块化标准,现在已被所有现代浏览器和Node.js支持。

// 导出命名导出
// utils.js
export const formatDate = (date) => {
  return new Date(date).toLocaleDateString();
};

export const debounce = (func, delay) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => func.apply(this, args), delay);
  };
};

// 导出函数
export function greet(name) {
  return `Hello, ${name}!`;
}

// 导出类
export class User {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    return `Hello, I'm ${this.name}`;
  }
}
// 导入命名导出
import { formatDate, debounce } from './utils.js';
import { greet } from './utils.js';

// 使用
console.log(formatDate('2024-01-15'));
const debouncedSearch = debounce(search, 300);

9. 默认导出

// 默认导出
// api.js
export default class API {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  async get(endpoint) {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    return response.json();
  }
  
  async post(endpoint, data) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });
    return response.json();
  }
}
// 导入默认导出
import API from './api.js';

const api = new API('https://api.example.com');
const users = await api.get('/users');

10. 混合导出

// 混合导出示例
// index.js

// 命名导出
export { formatDate, debounce } from './utils.js';
export { User } from './models.js';

// 默认导出
export default {
  version: '1.0.0',
  author: 'Developer'
};

// 重新导出并重命名
export { formatDate as format } from './utils.js';

// 导出所有
export * from './constants.js';

11. ES Modules的特点

// ES Modules的核心特性

// 1. 静态分析
// 在编译时确定依赖关系,可以进行tree-shaking

// 2. 异步加载
import('./module.js').then(module => {
  // 动态导入
  console.log(module.default);
});

// 3. 值的引用
// counter.js
export let count = 0;
export function increment() {
  count++;
}

// app.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1,因为是引用

// 4. 严格模式
// ES Modules自动运行在严格模式下
// 不能使用未声明的变量
// this指向undefined

模块化最佳实践

12. 模块组织结构

// 推荐的模块组织结构
// src/
//   ├── api/
//   │   ├── index.js        // 统一导出
//   │   ├── user.js         // 用户相关API
//   │   └── product.js      // 产品相关API
//   ├── components/
//   │   ├── Button/
//   │   │   ├── index.js
//   │   │   ├── Button.vue
//   │   │   └── Button.test.js
//   │   └── Form/
//   ├── utils/
//   │   ├── index.js
//   │   ├── date.js
//   │   └── string.js
//   └── main.js

// api/index.js - 统一导出
export { default as userAPI } from './user.js';
export { default as productAPI } from './product.js';

// 使用
import { userAPI, productAPI } from '@/api';

13. 循环依赖处理

// 循环依赖问题
// moduleA.js
import { funcB } from './moduleB.js';

export function funcA() {
  console.log('funcA called');
  funcB();
}

// moduleB.js
import { funcA } from './moduleA.js';

export function funcB() {
  console.log('funcB called');
  funcA();
}

// 解决方案1:延迟导入
// moduleA.js
export function funcA() {
  console.log('funcA called');
  import('./moduleB.js').then(module => {
    module.funcB();
  });
}

// 解决方案2:重构代码结构
// 将共享逻辑提取到第三个模块中

14. 动态导入与代码分割

// 动态导入实现代码分割
// router.js
const routes = [
  {
    path: '/home',
    component: () => import('./views/Home.vue')
  },
  {
    path: '/about',
    component: () => import('./views/About.vue')
  },
  {
    path: '/admin',
    component: () => import('./views/Admin.vue')
  }
];

// 条件导入
async function loadFeature() {
  if (shouldLoadFeature) {
    const module = await import('./feature.js');
    module.init();
  }
}

// 预加载
const preloadModule = import('./heavy-module.js');

// 当需要时使用
async function useModule() {
  const module = await preloadModule;
  module.doSomething();
}

现代构建工具中的模块化

15. Vite中的模块处理

// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  // 解析配置
  resolve: {
    // 路径别名
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils')
    },
    
    // 扩展名
    extensions: ['.js', '.json', '.vue']
  },
  
  // 构建优化
  build: {
    // 代码分割
    rollupOptions: {
      output: {
        manualChunks: {
          // 将node_modules中的代码打包到vendor
          'vendor': ['vue', 'vue-router', 'pinia'],
          // 第三方UI库
          'ui': ['element-plus']
        }
      }
    },
    
    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  }
});

16. Webpack中的模块处理

// webpack.config.js
const path = require('path');

module.exports = {
  // 入口配置
  entry: {
    main: './src/main.js',
    vendor: './src/vendor.js'
  },
  
  // 输出配置
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js'
  },
  
  // 模块解析
  resolve: {
    extensions: ['.js', '.json', '.vue'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components')
    }
  },
  
  // 优化配置
  optimization: {
    // 代码分割
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        },
        common: {
          name: 'common',
          minChunks: 2,
          priority: 5
        }
      }
    },
    
    // Tree Shaking
    usedExports: true,
    sideEffects: false
  }
};

性能优化

17. Tree Shaking优化

// utils.js
// 只导出需要的函数,避免打包无用代码
export const usedFunction = () => {
  console.log('这个函数会被使用');
};

export const unusedFunction = () => {
  console.log('这个函数不会被使用,会被tree-shaking移除');
};

// package.json
{
  "sideEffects": false,
  // 或者指定有副作用的文件
  "sideEffects": [
    "*.css",
    "./src/polyfill.js"
  ]
}

18. 模块预加载与预获取

// 预加载 - 高优先级
const preloadModule = () => {
  const link = document.createElement('link');
  link.rel = 'modulepreload';
  link.href = '/heavy-module.js';
  document.head.appendChild(link);
};

// 预获取 - 低优先级
const prefetchModule = () => {
  const link = document.createElement('link');
  link.rel = 'moduleprefetch';
  link.href = '/future-module.js';
  document.head.appendChild(link);
};

// 在路由导航时预加载
router.beforeEach((to, from, next) => {
  if (to.path === '/dashboard') {
    preloadModule();
  }
  next();
});

总结

JavaScript模块化经历了从无到有、从简单到复杂的演进过程:

发展历程

  1. 早期阶段:全局变量、命名空间
  2. CommonJS:Node.js服务端模块化
  3. AMD:浏览器端异步模块化
  4. ES Modules:官方标准,统一前后端

核心优势

  1. 代码组织:清晰的模块边界和职责
  2. 依赖管理:明确的依赖关系
  3. 可维护性:易于维护和重构
  4. 性能优化:支持tree-shaking和代码分割
  5. 开发体验:更好的IDE支持和类型推断

最佳实践

  1. 统一使用ES Modules:现代项目首选ESM
  2. 合理组织模块:按功能划分模块结构
  3. 避免循环依赖:重构代码结构解决循环依赖
  4. 利用动态导入:实现代码分割和懒加载
  5. 配置构建工具:充分利用tree-shaking等优化

学习建议

  1. 理解模块化的历史和演进
  2. 掌握ES Modules的语法和特性
  3. 学习构建工具的模块处理机制
  4. 实践性能优化技巧
  5. 关注模块化的最新发展

模块化是现代JavaScript开发的基础,掌握它对于成为一名优秀的前端开发者至关重要。随着JavaScript生态的不断发展,模块化技术也在不断演进,保持学习和实践是跟上技术发展的关键。


本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!

CSS容器查询:响应式设计的新范式

作者 bluceli
2026年3月9日 13:04

引言

在响应式设计的发展历程中,我们长期依赖媒体查询来根据视口大小调整布局。然而,现代Web应用的组件化特性使得基于视口的响应式设计显得力不从心。CSS容器查询的出现,为我们提供了一种基于容器尺寸而非视口尺寸的响应式设计新范式。本文将深入探讨CSS容器查询的核心概念、实际应用和最佳实践。

容器查询基础

1. 什么是容器查询

容器查询允许我们根据元素的容器尺寸来应用样式,而不是整个视口。这使得组件能够根据其所在容器的大小自适应调整,真正实现组件级别的响应式设计。

/* 定义容器 */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* 使用容器查询 */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

2. 容器查询的基本语法

容器查询的语法与媒体查询类似,但使用@container规则。

/* 定义容器 */
.sidebar {
  container-type: inline-size;
}

/* 容器查询示例 */
@container (min-width: 300px) {
  .widget {
    flex-direction: row;
  }
}

@container (min-width: 500px) {
  .widget {
    padding: 20px;
  }
}

容器类型与命名

3. 容器类型详解

CSS提供了两种容器类型:sizeinline-size

/* size容器 - 同时跟踪内联和块级尺寸 */
.gallery {
  container-type: size;
}

/* inline-size容器 - 只跟踪内联尺寸(推荐) */
.card-wrapper {
  container-type: inline-size;
}

/* 为什么推荐inline-size */
/* 1. 性能更好:浏览器只需要跟踪一个维度 */
/* 2. 避免循环依赖:减少布局计算复杂度 */

4. 容器命名

为容器命名可以创建更精确的查询规则。

/* 定义命名容器 */
.main-content {
  container-type: inline-size;
  container-name: main;
}

.sidebar {
  container-type: inline-size;
  container-name: side;
}

/* 针对不同容器的查询 */
@container main (min-width: 600px) {
  .article {
    font-size: 18px;
  }
}

@container side (min-width: 300px) {
  .widget {
    display: block;
  }
}

实际应用案例

5. 响应式卡片组件

创建一个根据容器大小自适应的卡片组件。

/* 卡片容器 */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* 基础卡片样式 */
.card {
  background: white;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* 小容器 - 垂直布局 */
@container card (max-width: 300px) {
  .card {
    display: flex;
    flex-direction: column;
  }
  
  .card-image {
    width: 100%;
    height: 150px;
  }
  
  .card-content {
    margin-top: 12px;
  }
}

/* 中等容器 - 水平布局 */
@container card (min-width: 301px) and (max-width: 500px) {
  .card {
    display: flex;
    flex-direction: row;
    gap: 16px;
  }
  
  .card-image {
    width: 120px;
    height: 120px;
    flex-shrink: 0;
  }
}

/* 大容器 - 网格布局 */
@container card (min-width: 501px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 20px;
  }
  
  .card-image {
    width: 100%;
    height: 200px;
    object-fit: cover;
  }
}

6. 自适应导航菜单

创建一个根据可用空间调整的导航菜单。

/* 导航容器 */
.nav-container {
  container-type: inline-size;
  container-name: nav;
}

/* 基础导航样式 */
.nav {
  display: flex;
  gap: 8px;
}

.nav-item {
  padding: 8px 16px;
  border-radius: 4px;
  transition: all 0.2s;
}

/* 窄容器 - 垂直菜单 */
@container nav (max-width: 400px) {
  .nav {
    flex-direction: column;
  }
  
  .nav-item {
    width: 100%;
    text-align: center;
  }
}

/* 中等容器 - 紧凑水平菜单 */
@container nav (min-width: 401px) and (max-width: 600px) {
  .nav-item {
    padding: 6px 12px;
    font-size: 14px;
  }
}

/* 宽容器 - 完整菜单 */
@container nav (min-width: 601px) {
  .nav {
    justify-content: space-between;
  }
  
  .nav-item {
    padding: 10px 20px;
  }
}

7. 响应式数据表格

创建一个根据容器宽度调整的表格。

/* 表格容器 */
.table-container {
  container-type: inline-size;
  container-name: table;
  overflow-x: auto;
}

/* 基础表格样式 */
.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table th,
.data-table td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #e5e7eb;
}

/* 窄容器 - 紧凑表格 */
@container table (max-width: 500px) {
  .data-table th,
  .data-table td {
    padding: 8px;
    font-size: 14px;
  }
  
  .data-table .description {
    display: none;
  }
}

/* 中等容器 - 标准表格 */
@container table (min-width: 501px) and (max-width: 800px) {
  .data-table th,
  .data-table td {
    padding: 10px 14px;
  }
}

/* 宽容器 - 宽松表格 */
@container table (min-width: 801px) {
  .data-table th,
  .data-table td {
    padding: 16px 20px;
  }
  
  .data-table {
    font-size: 16px;
  }
}

高级技巧

8. 容器查询单位

使用容器查询单位(cqwcqhcqmincqmax)创建相对尺寸。

.product-card {
  container-type: size;
  container-name: product;
}

/* 使用容器查询单位 */
@container product {
  .product-image {
    width: 50cqw;  /* 容器宽度的50% */
    height: 30cqh; /* 容器高度的30% */
  }
  
  .product-title {
    font-size: 2cqw;  /* 相对于容器宽度的字体大小 */
  }
  
  .badge {
    width: min(10cqw, 100px);  /* 结合min()函数 */
    height: min(10cqw, 100px);
  }
}

9. 嵌套容器查询

在嵌套结构中使用多层容器查询。

/* 外层容器 */
.dashboard {
  container-type: inline-size;
  container-name: dashboard;
}

/* 内层容器 */
.widget {
  container-type: inline-size;
  container-name: widget;
}

/* 外层容器查询 */
@container dashboard (min-width: 800px) {
  .dashboard {
    display: grid;
    grid-template-columns: 250px 1fr;
  }
}

/* 内层容器查询 */
@container widget (min-width: 300px) {
  .widget-content {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

10. 容器查询与JavaScript结合

使用JavaScript动态控制容器查询。

// 检测容器查询支持
class ContainerQueryDetector {
  constructor() {
    this.supported = CSS.supports('container-type', 'inline-size');
  }
  
  init() {
    if (!this.supported) {
      this.loadPolyfill();
    }
  }
  
  loadPolyfill() {
    // 加载容器查询polyfill
    const script = document.createElement('script');
    script.src = 'https://unpkg.com/container-query-polyfill';
    document.head.appendChild(script);
  }
  
  // 动态设置容器
  setContainer(element, name, type = 'inline-size') {
    if (this.supported) {
      element.style.containerType = type;
      element.style.containerName = name;
    } else {
      // 回退方案
      element.classList.add(`container-${name}`);
    }
  }
}

// 使用示例
const detector = new ContainerQueryDetector();
detector.init();

// 动态创建容器
const cardContainer = document.querySelector('.card-container');
detector.setContainer(cardContainer, 'card');

性能优化

11. 容器查询性能考虑

容器查询的性能优化策略。

/* 1. 优先使用inline-size而非size */
.efficient-container {
  container-type: inline-size; /* 更好的性能 */
}

/* 2. 避免过度嵌套 */
/* 不推荐 */
.outer {
  container-type: inline-size;
}
.middle {
  container-type: inline-size;
}
.inner {
  container-type: inline-size;
}

/* 推荐 - 扁平化结构 */
.outer {
  container-type: inline-size;
}

/* 3. 合并查询规则 */
/* 不推荐 */
@container (min-width: 300px) {
  .item { padding: 10px; }
}
@container (min-width: 300px) {
  .item { margin: 10px; }
}

/* 推荐 */
@container (min-width: 300px) {
  .item {
    padding: 10px;
    margin: 10px;
  }
}

12. 渐进增强策略

为不支持容器查询的浏览器提供回退方案。

/* 基础样式 - 所有浏览器 */
.card {
  display: flex;
  flex-direction: column;
  padding: 16px;
}

/* 媒体查询回退 */
@media (min-width: 768px) {
  .card {
    flex-direction: row;
  }
}

/* 容器查询 - 现代浏览器 */
@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
  
  @container (min-width: 400px) {
    .card {
      flex-direction: row;
    }
  }
}

实际项目应用

13. 电商产品卡片

完整的电商产品卡片实现。

/* 产品卡片容器 */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}

.product-card {
  container-type: inline-size;
  container-name: product;
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* 产品图片 */
.product-image {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  transition: transform 0.3s;
}

.product-card:hover .product-image {
  transform: scale(1.05);
}

/* 产品信息 */
.product-info {
  padding: 16px;
}

.product-title {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 8px;
}

.product-price {
  font-size: 18px;
  font-weight: 700;
  color: #e53e3e;
}

/* 容器查询变体 */
@container product (min-width: 300px) {
  .product-title {
    font-size: 18px;
  }
  
  .product-price {
    font-size: 20px;
  }
}

@container product (min-width: 400px) {
  .product-info {
    padding: 20px;
  }
  
  .product-title {
    font-size: 20px;
  }
  
  .product-description {
    display: block;
    margin-top: 8px;
    color: #666;
    font-size: 14px;
  }
}

14. 响应式文章布局

自适应的文章内容布局。

/* 文章容器 */
.article-wrapper {
  container-type: inline-size;
  container-name: article;
  max-width: 100%;
}

/* 文章内容 */
.article-content {
  line-height: 1.8;
  color: #333;
}

.article-content h2 {
  font-size: 24px;
  margin: 32px 0 16px;
}

.article-content p {
  margin-bottom: 16px;
}

/* 容器查询适配 */
@container article (max-width: 400px) {
  .article-content {
    font-size: 16px;
  }
  
  .article-content h2 {
    font-size: 20px;
  }
  
  .article-content img {
    width: 100%;
    height: auto;
  }
}

@container article (min-width: 401px) and (max-width: 700px) {
  .article-content {
    font-size: 17px;
  }
  
  .article-content h2 {
    font-size: 22px;
  }
  
  .article-content img {
    max-width: 100%;
  }
}

@container article (min-width: 701px) {
  .article-content {
    font-size: 18px;
    max-width: 650px;
    margin: 0 auto;
  }
  
  .article-content h2 {
    font-size: 26px;
  }
  
  .article-content img {
    max-width: 100%;
    border-radius: 8px;
  }
}

调试与测试

15. 容器查询调试工具

使用开发者工具调试容器查询。

// 容器查询调试器
class ContainerQueryDebugger {
  constructor() {
    this.containers = new Map();
  }
  
  // 注册容器
  registerContainer(element, name) {
    this.containers.set(name, element);
    this.addDebugOverlay(element, name);
  }
  
  // 添加调试覆盖层
  addDebugOverlay(element, name) {
    const overlay = document.createElement('div');
    overlay.style.cssText = `
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      border: 2px dashed #ff6b6b;
      pointer-events: none;
      z-index: 9999;
    `;
    
    const label = document.createElement('div');
    label.textContent = name;
    label.style.cssText = `
      position: absolute;
      top: -20px;
      left: 0;
      background: #ff6b6b;
      color: white;
      padding: 2px 8px;
      font-size: 12px;
      border-radius: 4px;
    `;
    
    overlay.appendChild(label);
    element.style.position = 'relative';
    element.appendChild(overlay);
    
    // 监听尺寸变化
    this.observeSize(element, name);
  }
  
  // 监听容器尺寸变化
  observeSize(element, name) {
    const resizeObserver = new ResizeObserver(entries => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        console.log(`容器 ${name} 尺寸:`, {
          width: Math.round(width),
          height: Math.round(height)
        });
      }
    });
    
    resizeObserver.observe(element);
  }
}

// 使用示例
const debugger = new ContainerQueryDebugger();
debugger.registerContainer(
  document.querySelector('.card-container'),
  'card'
);

总结

CSS容器查询为响应式设计带来了革命性的变化:

核心优势

  1. 组件级响应式:组件根据自身容器而非视口调整
  2. 更好的复用性:组件在任何容器中都能正确显示
  3. 减少媒体查询:不再依赖全局视口尺寸
  4. 更精确的控制:基于实际可用空间调整布局

最佳实践

  1. 优先使用inline-size:性能更好,避免循环依赖
  2. 合理命名容器:提高代码可读性和维护性
  3. 渐进增强:为旧浏览器提供回退方案
  4. 性能优化:避免过度嵌套和冗余查询

应用场景

  • 组件库开发:创建真正自适应的UI组件
  • 复杂布局:多列、网格等灵活布局
  • 内容适配:根据可用空间调整内容显示
  • 响应式设计:替代或补充传统媒体查询

容器查询代表了CSS响应式设计的未来方向。随着浏览器支持的不断完善,它将成为现代Web开发的标准工具。开始在你的项目中使用容器查询,体验组件级响应式设计的强大能力!


本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!

昨天以前首页

WebAssembly实战指南:将高性能计算带入浏览器

作者 bluceli
2026年3月7日 15:35

引言

WebAssembly(简称Wasm)是一种新型的代码格式,可以在现代Web浏览器中运行。它为Web平台带来了接近原生的性能,使得C++、Rust等语言编写的代码能够在浏览器中高效运行。本文将深入探讨WebAssembly的8大核心特性,帮助你掌握这个将高性能计算带入浏览器的强大技术。

WebAssembly基础概念

1. 什么是WebAssembly

WebAssembly是一种二进制指令格式,专为高效的执行和紧凑的表示而设计。它不是要替代JavaScript,而是与JavaScript协同工作,让Web应用能够利用多种语言编写的高性能代码库。

// WebAssembly的主要特点
// 1. 二进制格式:体积小,加载快
// 2. 接近原生性能:执行速度快
// 3. 多语言支持:C/C++、Rust、Go等
// 4. 安全性:在沙箱环境中运行
// 5. 可移植性:跨平台兼容

2. WebAssembly与JavaScript的关系

// JavaScript和WebAssembly的互补关系
// JavaScript:适合UI交互、DOM操作、业务逻辑
// WebAssembly:适合计算密集型任务、图像处理、加密解密

// 典型的使用场景
const scenarios = {
  imageProcessing: '图像滤镜、视频编解码',
  gameDevelopment: '3D游戏引擎、物理模拟',
  dataProcessing: '大数据分析、机器学习推理',
  cryptography: '加密解密、哈希计算',
  scientificComputing: '数学计算、物理模拟'
};

编译和加载WebAssembly

3. 从C++编译到WebAssembly

使用Emscripten工具链将C++代码编译为WebAssembly。

// simple.cpp - C++源代码
#include <emscripten.h>

extern "C" {
  // 导出函数给JavaScript调用
  EMSCRIPTEN_KEEPALIVE
  int add(int a, int b) {
    return a + b;
  }
  
  EMSCRIPTEN_KEEPALIVE
  int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}
# 编译命令
emcc simple.cpp -o simple.js \
  -s EXPORTED_FUNCTIONS='["_add","_fibonacci"]' \
  -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' \
  optimiz

4. 在JavaScript中加载WebAssembly

// 方法1:使用Emscripten生成的胶水代码
const Module = require('./simple.js');

Module.onRuntimeInitialized = function() {
  // 调用C++函数
  const result = Module._add(10, 20);
  console.log('10 + 20 =', result);
  
  const fib = Module._fibonacci(10);
  console.log('fibonacci(10) =', fib);
};

// 方法2:直接加载.wasm文件
async function loadWebAssembly(url) {
  const response = await fetch(url);
  const buffer = await response.arrayBuffer();
  const module = await WebAssembly.instantiate(buffer);
  return module.instance.exports;
}

// 使用示例
loadWebAssembly('simple.wasm').then(exports => {
  console.log(exports.add(10, 20));
  console.log(exports.fibonacci(10));
});

JavaScript与WebAssembly交互

5. 数据类型转换

WebAssembly和JavaScript之间的数据类型需要正确转换。

// WebAssembly只支持有限的数值类型
// i32: 32位整数
// i64: 64位整数
// f32: 32位浮点数
// f64: 64位浮点数

// JavaScript与WebAssembly的交互
class WasmModule {
  constructor(module) {
    this.exports = module.exports;
    this.memory = module.exports.memory || new WebAssembly.Memory({ initial: 256 });
  }
  
  // 传递字符串到WebAssembly
  writeString(str) {
    const encoder = new TextEncoder();
    const bytes = encoder.encode(str);
    
    // 分配内存
    const offset = this.exports.malloc(bytes.length + 1);
    
    // 写入内存
    const view = new Uint8Array(this.memory.buffer);
    view.set(bytes, offset);
    view[offset + bytes.length] = 0; // null终止符
    
    return offset;
  }
  
  // 从WebAssembly读取字符串
  readString(offset) {
    const view = new Uint8Array(this.memory.buffer);
    let length = 0;
    
    // 计算字符串长度
    while (view[offset + length] !== 0) {
      length++;
    }
    
    // 读取字符串
    const bytes = view.slice(offset, offset + length);
    const decoder = new TextDecoder();
    return decoder.decode(bytes);
  }
}

// 使用示例
const wasm = new WasmModule(module);
const strOffset = wasm.writeString('Hello WebAssembly');
const result = wasm.exports.processString(strOffset);
const output = wasm.readString(result);

6. 处理复杂数据结构

// 处理数组和对象
class ArrayHandler {
  constructor(module) {
    this.exports = module.exports;
    this.memory = module.exports.memory;
  }
  
  // 创建数组
  createArray(type, values) {
    const bytesPerElement = this.getBytesPerElement(type);
    const array = new (this.getTypedArray(type))(
      this.memory.buffer,
      this.exports.malloc(values.length * bytesPerElement),
      values.length
    );
    
    array.set(values);
    return array.byteOffset;
  }
  
  // 读取数组
  readArray(type, offset, length) {
    const TypedArray = this.getTypedArray(type);
    return new TypedArray(
      this.memory.buffer,
      offset,
      length
    );
  }
  
  getBytesPerElement(type) {
    const sizes = {
      'i32': 4,
      'f32': 4,
      'f64': 8
    };
    return sizes[type] || 4;
  }
  
  getTypedArray(type) {
    const types = {
      'i32': Int32Array,
      'f32': Float32Array,
      'f64': Float64Array
    };
    return types[type] || Int32Array;
  }
}

// 使用示例
const handler = new ArrayHandler(module);
const arrayOffset = handler.createArray('f32', [1.0, 2.0, 3.0, 4.0]);
const result = handler.exports.processArray(arrayOffset, 4);
const output = handler.readArray('f32', result, 4);

实战应用案例

7. 图像处理应用

使用WebAssembly实现高性能的图像滤镜。

// image_filter.cpp
#include <emscripten.h>
#include <stdint.h>

extern "C" {
  EMSCRIPTEN_KEEPALIVE
  void grayscale(uint8_t* data, int width, int height) {
    for (int i = 0; i < width * height * 4; i += 4) {
      uint8_t r = data[i];
      uint8_t g = data[i + 1];
      uint8_t b = data[i + 2];
      
      // 计算灰度值
      uint8_t gray = 0.299 * r + 0.587 * g + 0.114 * b;
      
      data[i] = gray;
      data[i + 1] = gray;
      data[i + 2] = gray;
    }
  }
  
  EMSCRIPTEN_KEEPALIVE
  void invert(uint8_t* data, int width, int height) {
    for (int i = 0; i < width * height * 4; i += 4) {
      data[i] = 255 - data[i];         // R
      data[i + 1] = 255 - data[i + 1]; // G
      data[i + 2] = 255 - data[i + 2]; // B
    }
  }
}
// JavaScript调用
class ImageProcessor {
  constructor() {
    this.wasm = null;
  }
  
  async init() {
    const response = await fetch('image_filter.wasm');
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.instantiate(buffer);
    this.wasm = module.instance.exports;
  }
  
  applyGrayscale(imageData) {
    const { data, width, height } = imageData;
    
    // 创建WebAssembly内存视图
    const wasmMemory = new Uint8Array(
      this.wasm.memory.buffer,
      this.wasm.malloc(data.length),
      data.length
    );
    
    // 复制图像数据
    wasmMemory.set(data);
    
    // 调用WebAssembly函数
    this.wasm.grayscale(wasmMemory.byteOffset, width, height);
    
    // 获取处理后的数据
    const result = new Uint8ClampedArray(wasmMemory);
    
    // 释放内存
    this.wasm.free(wasmMemory.byteOffset);
    
    return new ImageData(result, width, height);
  }
}

// 使用示例
const processor = new ImageProcessor();
await processor.init();

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

const processedData = processor.applyGrayscale(imageData);
ctx.putImageData(processedData, 0, 0);

8. 加密解密应用

// crypto.cpp
#include <emscripten.h>
#include <stdint.h>
#include <string.h>

extern "C" {
  EMSCRIPTEN_KEEPALIVE
  void xorEncrypt(uint8_t* data, int length, uint8_t key) {
    for (int i = 0; i < length; i++) {
      data[i] ^= key;
    }
  }
  
  EMSCRIPTEN_KEEPALIVE
  void simpleHash(uint8_t* data, int length, uint8_t* output) {
    uint32_t hash = 0;
    
    for (int i = 0; i < length; i++) {
      hash = hash * 31 + data[i];
    }
    
    memcpy(output, &hash, 4);
  }
}
// JavaScript加密工具
class CryptoTool {
  constructor() {
    this.wasm = null;
  }
  
  async init() {
    const response = await fetch('crypto.wasm');
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.instantiate(buffer);
    this.wasm = module.instance.exports;
  }
  
  encrypt(data, key) {
    const encoder = new TextEncoder();
    const bytes = encoder.encode(data);
    
    // 分配内存
    const wasmData = new Uint8Array(
      this.wasm.memory.buffer,
      this.wasm.malloc(bytes.length),
      bytes.length
    );
    
    wasmData.set(bytes);
    
    // 加密
    this.wasm.xorEncrypt(wasmData.byteOffset, bytes.length, key);
    
    // 获取结果
    const encrypted = new Uint8Array(wasmData);
    
    // 释放内存
    this.wasm.free(wasmData.byteOffset);
    
    return btoa(String.fromCharCode(...encrypted));
  }
  
  decrypt(encryptedData, key) {
    const bytes = new Uint8Array(
      atob(encryptedData).split('').map(c => c.charCodeAt(0))
    );
    
    const wasmData = new Uint8Array(
      this.wasm.memory.buffer,
      this.wasm.malloc(bytes.length),
      bytes.length
    );
    
    wasmData.set(bytes);
    
    // 解密(XOR加密解密相同)
    this.wasm.xorEncrypt(wasmData.byteOffset, bytes.length, key);
    
    const decrypted = new Uint8Array(wasmData);
    this.wasm.free(wasmData.byteOffset);
    
    const decoder = new TextDecoder();
    return decoder.decode(decrypted);
  }
}

// 使用示例
const crypto = new CryptoTool();
await crypto.init();

const message = 'Hello WebAssembly!';
const key = 42;

const encrypted = crypto.encrypt(message, key);
console.log('加密:', encrypted);

const decrypted = crypto.decrypt(encrypted, key);
console.log('解密:', decrypted);

性能优化技巧

9. 内存管理优化

// 内存池管理
class MemoryPool {
  constructor(module, initialSize = 1024 * 1024) {
    this.module = module;
    this.pool = [];
    this.allocated = new Set();
    this.chunkSize = initialSize;
  }
  
  allocate(size) {
    // 查找合适的内存块
    for (let i = 0; i < this.pool.length; i++) {
      const block = this.pool[i];
      if (!block.used && block.size >= size) {
        block.used = true;
        this.allocated.add(block.offset);
        return block.offset;
      }
    }
    
    // 分配新内存块
    const offset = this.module.exports.malloc(size);
    this.pool.push({
      offset,
      size,
      used: true
    });
    this.allocated.add(offset);
    
    return offset;
  }
  
  free(offset) {
    const block = this.pool.find(b => b.offset === offset);
    if (block) {
      block.used = false;
      this.allocated.delete(offset);
    }
  }
  
  cleanup() {
    // 释放所有未使用的内存块
    this.pool.forEach(block => {
      if (!block.used) {
        this.module.exports.free(block.offset);
      }
    });
    
    this.pool = this.pool.filter(block => block.used);
  }
}

10. 多线程处理

// 使用Web Worker和WebAssembly
class WasmWorker {
  constructor(wasmUrl) {
    this.worker = new Worker(URL.createObjectURL(
      new Blob([`
        let wasmModule = null;
        
        self.onmessage = async function(e) {
          const { type, data } = e.data;
          
          if (type === 'init') {
            const response = await fetch(data.wasmUrl);
            const buffer = await response.arrayBuffer();
            wasmModule = await WebAssembly.instantiate(buffer);
            self.postMessage({ type: 'ready' });
          } else if (type === 'compute') {
            const result = wasmModule.instance.exports[data.function](
              ...data.args
            );
            self.postMessage({ type: 'result', data: result });
          }
        };
      `], { type: 'application/javascript' })
    ));
  }
  
  async init() {
    return new Promise((resolve) => {
      this.worker.onmessage = (e) => {
        if (e.data.type === 'ready') {
          resolve();
        }
      };
      
      this.worker.postMessage({
        type: 'init',
        data: { wasmUrl: this.wasmUrl }
      });
    });
  }
  
  compute(functionName, ...args) {
    return new Promise((resolve) => {
      this.worker.onmessage = (e) => {
        if (e.data.type === 'result') {
          resolve(e.data.data);
        }
      };
      
      this.worker.postMessage({
        type: 'compute',
        data: { function: functionName, args }
      });
    });
  }
}

// 使用示例
const worker = new WasmWorker('compute.wasm');
await worker.init();

// 并行计算
const results = await Promise.all([
  worker.compute('heavyComputation', 1000),
  worker.compute('heavyComputation', 2000),
  worker.compute('heavyComputation', 3000)
]);

调试和测试

11. WebAssembly调试技巧

// 调试工具
class WasmDebugger {
  constructor(module) {
    this.exports = module.exports;
    this.memory = module.exports.memory;
    this.breakpoints = new Set();
  }
  
  // 内存检查
  inspectMemory(offset, length) {
    const view = new Uint8Array(this.memory.buffer);
    const bytes = [];
    
    for (let i = 0; i < length; i++) {
      bytes.push(view[offset + i].toString(16).padStart(2, '0'));
    }
    
    return bytes.join(' ');
  }
  
  // 性能监控
  profileFunction(funcName, ...args) {
    const startTime = performance.now();
    const result = this.exports[funcName](...args);
    const endTime = performance.now();
    
    console.log(`${funcName} 执行时间: ${(endTime - startTime).toFixed(2)}ms`);
    return result;
  }
  
  // 内存使用统计
  getMemoryUsage() {
    const memory = this.exports.memory;
    return {
      used: memory.buffer.byteLength,
      total: memory.buffer.byteLength,
      pages: memory.buffer.byteLength / 65536
    };
  }
}

// 使用示例
const debugger = new WasmDebugger(module);

console.log('内存内容:', debugger.inspectMemory(0, 16));
console.log('内存使用:', debugger.getMemoryUsage());

const result = debugger.profileFunction('fibonacci', 30);

12. 单元测试

// WebAssembly测试框架
class WasmTester {
  constructor(module) {
    this.exports = module.exports;
    this.tests = [];
  }
  
  test(name, fn) {
    this.tests.push({ name, fn });
  }
  
  async run() {
    const results = [];
    
    for (const test of this.tests) {
      try {
        await test.fn(this.exports);
        results.push({ name: test.name, passed: true });
        console.log(`✓ ${test.name}`);
      } catch (error) {
        results.push({ name: test.name, passed: false, error: error.message });
        console.log(`✗ ${test.name}: ${error.message}`);
      }
    }
    
    return results;
  }
}

// 使用示例
const tester = new WasmTester(module);

tester.test('add函数测试', (exports) => {
  const result = exports.add(10, 20);
  if (result !== 30) {
    throw new Error(`期望30,实际得到${result}`);
  }
});

tester.test('fibonacci函数测试', (exports) => {
  const result = exports.fibonacci(10);
  if (result !== 55) {
    throw new Error(`期望55,实际得到${result}`);
  }
});

const results = await tester.run();
const passed = results.filter(r => r.passed).length;
console.log(`测试通过: ${passed}/${results.length}`);

总结

WebAssembly为Web平台带来了革命性的性能提升:

核心优势

  1. 高性能:接近原生的执行速度
  2. 多语言支持:C/C++、Rust、Go等
  3. 安全性:在沙箱环境中运行
  4. 可移植性:跨平台兼容
  5. 与JavaScript协同:完美互补

适用场景

  • 计算密集型任务:图像处理、视频编解码
  • 游戏开发:3D引擎、物理模拟
  • 数据处理:大数据分析、机器学习
  • 加密解密:密码学运算
  • 科学计算:数学计算、物理模拟

最佳实践

  1. 合理选择:使用WebAssembly处理性能关键代码
  2. 内存管理:注意内存分配和释放
  3. 数据转换:正确处理JavaScript和WebAssembly之间的数据类型
  4. 性能监控:持续监控和优化性能
  5. 错误处理:完善的错误处理和调试机制

学习路径

  1. 理解WebAssembly的基本概念
  2. 学习编译工具链(Emscripten)
  3. 掌握JavaScript与WebAssembly的交互
  4. 实践实际应用案例
  5. 学习性能优化技巧

WebAssembly正在改变Web开发的格局,让浏览器能够运行更复杂、更强大的应用。开始在你的项目中探索WebAssembly吧,体验高性能Web开发的全新可能!


本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!

Vue 3 Composition API深度解析:构建可复用逻辑的终极方案

作者 bluceli
2026年3月7日 10:13

引言

Vue 3的Composition API是Vue框架最重大的更新之一,它提供了一种全新的组件逻辑组织方式。与传统的Options API相比,Composition API让我们能够更灵活地组织和复用代码逻辑。本文将深入探讨Vue 3 Composition API的8大核心特性,帮助你掌握这个构建可复用逻辑的终极方案。

setup函数基础

1. setup函数的基本使用

setup函数是Composition API的入口点,它在组件创建之前执行。

import { ref, reactive } from 'vue';

export default {
  setup() {
    // 定义响应式数据
    const count = ref(0);
    const user = reactive({
      name: 'Vue 3',
      version: '3.0'
    });

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

    // 返回给模板使用
    return {
      count,
      user,
      increment
    };
  }
};

2. setup函数的参数

setup函数接收两个参数:props和context。

export default {
  props: {
    title: String,
    initialCount: {
      type: Number,
      default: 0
    }
  },
  setup(props, context) {
    // props是响应式的,不能解构
    console.log(props.title);
    
    // context包含attrs、slots、emit等
    const { attrs, slots, emit } = context;
    
    // 触发事件
    const handleClick = () => {
      emit('update', props.initialCount + 1);
    };
    
    return { handleClick };
  }
};

响应式API详解

3. ref与reactive的选择

ref和reactive是创建响应式数据的两种方式,各有适用场景。

import { ref, reactive, toRefs } from 'vue';

// ref - 适合基本类型和单一对象
const count = ref(0);
const message = ref('Hello');

// 访问ref的值需要.value
console.log(count.value);
count.value++;

// reactive - 适合复杂对象
const state = reactive({
  count: 0,
  user: {
    name: 'Vue',
    age: 3
  }
});

// 访问reactive的值不需要.value
console.log(state.count);
state.count++;

// 在模板中自动解包,不需要.value
// <template>
//   <div>{{ count }}</div>
//   <div>{{ state.count }}</div>
// </template>

4. toRefs的使用

当需要从reactive对象中解构属性时,使用toRefs保持响应性。

import { reactive, toRefs } from 'vue';

export default {
  setup() {
    const state = reactive({
      count: 0,
      name: 'Vue 3',
      isActive: true
    });

    // 不推荐 - 失去响应性
    // const { count, name } = state;

    // 推荐 - 使用toRefs保持响应性
    const { count, name, isActive } = toRefs(state);

    const increment = () => {
      count.value++;
    };

    return {
      count,
      name,
      isActive,
      increment
    };
  }
};

计算属性与侦听器

5. computed计算属性

computed用于创建计算属性,支持getter和setter。

import { ref, computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    // 只读计算属性
    const fullName = computed(() => {
      return firstName.value + ' ' + lastName.value;
    });

    // 可写计算属性
    const writableFullName = computed({
      get() {
        return firstName.value + ' ' + lastName.value;
      },
      set(value) {
        const [first, last] = value.split(' ');
        firstName.value = first;
        lastName.value = last;
      }
    });

    return {
      firstName,
      lastName,
      fullName,
      writableFullName
    };
  }
};

6. watch与watchEffect

watch和watchEffect用于侦听数据变化。

import { ref, reactive, watch, watchEffect } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const user = reactive({
      name: 'Vue',
      age: 3
    });

    // watchEffect - 自动追踪依赖
    watchEffect(() => {
      console.log(`Count is: ${count.value}`);
      console.log(`User is: ${user.name}`);
    });

    // watch - 显式指定侦听源
    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
    });

    // 侦听多个源
    watch([count, () => user.name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`Count: ${oldCount} -> ${newCount}, Name: ${oldName} -> ${newName}`);
    });

    // watch的配置选项
    watch(
      () => user.name,
      (newValue) => {
        console.log(`Name changed to: ${newValue}`);
      },
      {
        immediate: true,  // 立即执行
        deep: true        // 深度侦听
      }
    );

    return { count, user };
  }
};

生命周期钩子

7. 生命周期钩子的使用

Composition API中的生命周期钩子以on开头。

import { 
  onMounted, 
  onUpdated, 
  onUnmounted,
  onBeforeMount,
  onBeforeUpdate,
  onBeforeUnmount
} from 'vue';

export default {
  setup() {
    onBeforeMount(() => {
      console.log('组件挂载前');
    });

    onMounted(() => {
      console.log('组件已挂载');
      // 可以在这里访问DOM
    });

    onBeforeUpdate(() => {
      console.log('组件更新前');
    });

    onUpdated(() => {
      console.log('组件已更新');
    });

    onBeforeUnmount(() => {
      console.log('组件卸载前');
    });

    onUnmounted(() => {
      console.log('组件已卸载');
      // 清理工作
    });

    return {};
  }
};

自定义组合函数

8. 创建可复用的逻辑

自定义组合函数是Composition API的核心优势,让我们能够提取和复用逻辑。

// useCounter.js - 计数器逻辑
import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  const reset = () => {
    count.value = initialValue;
  };

  const double = computed(() => count.value * 2);

  return {
    count,
    increment,
    decrement,
    reset,
    double
  };
}

// useMouse.js - 鼠标位置追踪
import { ref, onMounted, onUnmounted } from 'vue';

export function useMouse() {
  const x = ref(0);
  const y = ref(0);

  const update = (event) => {
    x.value = event.pageX;
    y.value = event.pageY;
  };

  onMounted(() => {
    window.addEventListener('mousemove', update);
  });

  onUnmounted(() => {
    window.removeEventListener('mousemove', update);
  });

  return { x, y };
}

// 在组件中使用
import { useCounter, useMouse } from './composables';

export default {
  setup() {
    const { count, increment, decrement, double } = useCounter(10);
    const { x, y } = useMouse();

    return {
      count,
      increment,
      decrement,
      double,
      x,
      y
    };
  }
};

依赖注入

9. provide与inject

provide和inject用于跨组件层级传递数据。

// 父组件
import { provide, ref } from 'vue';

export default {
  setup() {
    const theme = ref('dark');
    const user = ref({
      name: 'Vue User',
      role: 'admin'
    });

    // 提供数据
    provide('theme', theme);
);
    provide('user', user);

    return { theme };
  }
};

// 子组件
import { inject } from 'vue';

export default {
  setup() {
    // 注入数据
    const theme = inject('theme');
    const user = inject('user');

    // 提供默认值
    const config = inject('config', {
      debug: false,
      version: '1.0'
    });

    return { theme, user, config };
  }
};

模板引用

10. 使用ref获取DOM元素

在Composition API中使用ref获取模板引用。

import { ref, onMounted } from 'vue';

export default {
  setup() {
    // 创建模板引用
    const inputRef = ref(null);
    const listRef = ref(null);

    onMounted(() => {
      // 访问DOM元素
      inputRef.value.focus();
      
      // 访问组件实例
      console.log(listRef.value.items);
    });

    const focusInput = () => {
      inputRef.value.focus();
    };

    return {
      inputRef,
      listRef,
      focusInput
    };
  }
};

// 模板中使用
// <template>
//   <input ref="inputRef" />
//   <MyList ref="listRef" />
// </template>

实战案例

11. 表单处理组合函数

// useForm.js
import { ref, reactive } from 'vue';

export function useForm(initialValues, validationRules) {
  const values = reactive({ ...initialValues });
  const errors = reactive({});
  const touched = reactive({});

  const validate = () => {
    let isValid = true;
    
    for (const field in validationRules) {
      const rules = validationRules[field];
      const value = values[field];
      
      for (const rule of rules) {
        if (rule.required && !value) {
          errors[field] = rule.message || '此字段必填';
          isValid = false;
          break;
        }
        
        if (rule.pattern && !rule.pattern.test(value)) {
          errors[field] = rule.message || '格式不正确';
          isValid = false;
          break;
        }
        
        if (rule.validator && !rule.validator(value)) {
          errors[field] = rule.message || '验证失败';
          isValid = false;
          break;
        }
      }
    }
    
    return isValid;
  };

  const handleChange = (field) => (event) => {
    values[field] = event.target.value;
    touched[field] = true;
    
    if (errors[field]) {
      validate();
    }
  };

  const handleBlur = (field) => () => {
    touched[field] = true;
    validate();
  };

  const reset = () => {
    Object.assign(values, initialValues);
    Object.keys(errors).forEach(key => {
      errors[key] = '';
    });
    Object.keys(touched).forEach(key => {
      touched[key] = false;
    });
  };

  const submit = (callback) => () => {
    if (validate()) {
      callback(values);
    }
  };

  return {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    validate,
    reset,
    submit
  };
}

// 使用示例
export default {
  setup() {
    const { values, errors, handleChange, handleBlur, submit } = useForm(
      {
        username: '',
        email: '',
        password: ''
      },
      {
        username: [
          { required: true, message: '用户名必填' },
          { pattern: /^[a-zA-Z0-9_]{3,20}$/, message: '用户名格式不正确' }
        ],
        email: [
          { required: true, message: '邮箱必填' },
          { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '邮箱格式不正确' }
        ],
        password: [
          { required: true, message: '密码必填' },
          { validator: (value) => value.length >= 6, message: '密码至少6位' }
        ]
      }
    );

    const handleSubmit = submit((formData) => {
      console.log('表单提交:', formData);
      // 发送API请求
    });

    return {
      values,
      errors,
      handleChange,
      handleBlur,
      handleSubmit
    };
  }
};

12. 异步数据获取组合函数

// useAsyncData.js
import { ref, onMounted } from 'vue';

export function useAsyncData(fetchFn, options = {}) {
  const {
    immediate = true,
    initialData = null,
    onSuccess,
    onError
  } = options;

  const data = ref(initialData);
  const loading = ref(false);
  const error = ref(null);

  const execute = async (...args) => {
    loading.value = true;
    error.value = null;

    try {
      const result = await fetchFn(...args);
      data.value = result;
      
      if (onSuccess) {
        onSuccess(result);
      }
      
      return result;
    } catch (err) {
      error.value = err;
      
      if (onError) {
        onError(err);
      }
      
      throw err;
    } finally {
      loading.value = false;
    }
  };

  if (immediate) {
    onMounted(execute);
  }

  return {
    data: data,
    loading: loading,
    error: error,
    execute: execute,
    refresh: execute
  };
}

// 使用示例
export default {
  setup() {
    const { data, loading, error, refresh } = useAsyncData(
      async (userId) => {
        const response = await fetch(`/api/users/${userId}`);
        return response.json();
      },
      {
        immediate: true,
        onSuccess: (data) => {
          console.log('数据加载成功:', data);
        },
        onError: (error) => {
          console.error('数据加载失败:', error);
        }
      }
    );

    return {
      data,
      loading,
      error,
      refresh
    };
  }
};

总结

Vue 3 Composition API为我们提供了更强大、更灵活的代码组织方式:

核心优势

  1. 逻辑复用:通过自定义组合函数轻松复用逻辑
  2. 代码组织:相关逻辑可以组织在一起,而不是分散在options中
  3. 类型推断:更好的TypeScript支持
  4. 灵活性:更灵活的代码组织方式

最佳实践

  1. 合理使用ref和reactive:基本类型用ref,复杂对象用reactive
  2. 提取组合函数:将可复用逻辑提取为独立的组合函数
  3. 保持单一职责:每个组合函数只负责一个功能
  4. 善用toRefs:解构reactive对象时使用toRefs保持响应性
  5. 合理使用生命周期:在setup中正确使用生命周期钩子

学习路径

  1. 掌握setup函数和响应式API
  2. 学习computed和watch的使用
  3. 理解生命周期钩子
  4. 实践自定义组合函数
  5. 掌握依赖注入和模板引用

Composition API不仅是一种新的API,更是一种新的思维方式。它让我们能够以更函数式、更模块化的方式组织代码,提高了代码的可维护性和可测试性。开始在你的项目中使用Composition API吧,体验Vue 3带来的全新开发体验!


本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!

JavaScript异步编程深度解析:从回调到Async Await的演进之路

作者 bluceli
2026年3月6日 10:57

引言

JavaScript作为单线程语言,异步编程是其核心特性之一。从最初的回调函数,到Promise,再到Async/Await语法糖,JavaScript的异步编程方式经历了多次演进。本文将深入探讨JavaScript异步编程的8大核心概念,帮助你全面掌握异步编程的艺术。

回调函数时代

1. 基础回调模式

回调函数是JavaScript最早的异步处理方式,虽然简单但容易产生"回调地狱"。

// 基础回调示例
function fetchData(callback) {
  setTimeout(() => {
    const data = { id: 1, name: 'JavaScript' };
    callback(data);
  }, 1000);
}

// 使用回调
fetchData(function(data) {
  console.log('获取到数据:', data);
});

2. 回调地狱问题

当多个异步操作需要按顺序执行时,回调函数会嵌套过深,代码难以维护。

// 回调地狱示例
fetchData(function(data1) {
  processData(data1, function(data2) {
    saveData(data2, function(result) {
      notifyUser(result, function() {
        console.log('所有操作完成');
      });
    });
  });
});

3. 错误处理困境

回调函数的错误处理比较复杂,需要通过额外的参数传递错误信息。

// Node.js风格的错误优先回调
function readFile(filename, callback) {
  fs.readFile(filename, (err, data) => {
    if (err) {
      callback(err, null);
    } else {
      callback(null, data);
    }
  });
}

// 使用示例
readFile('config.json', (err, data) => {
  if (err) {
    console.error('读取文件失败:', err);
    return;
  }
  console.log('文件内容:', data);
});

Promise革命

4. Promise基础用法

Promise的出现解决了回调地狱的问题,提供了更优雅的异步处理方式。

// 创建Promise
function fetchUserData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId > 0) {
        resolve({ id: userId, name: '用户' + userId });
      } else {
        reject(new Error('无效的用户ID'));
      }
    }, 1000);
  });
}

// 使用Promise
fetchUserData(1)
  .then(user => {
    console.log('用户信息:', user);
    return user;
  })
  .catch(error => {
    console.error('获取用户失败:', error);
  });

5. Promise链式调用

Promise支持链式调用,可以优雅地处理多个异步操作。

// Promise链式调用
fetchUserData(1)
  .then(user => {
    console.log('步骤1: 获取用户', user);
    return fetchUserPosts(user.id);
  })
  .then(posts => {
    console.log('步骤2: 获取文章', posts);
    return fetchPostComments(posts[0].id);
  })
  .then(comments => {
    console.log('步骤3: 获取评论', comments);
  })
  .catch(error => {
    console.error('发生错误:', error);
  });

6. Promise静态方法

Promise提供了多个静态方法,用于处理多个Promise实例。

// Promise.all - 所有Promise都成功才成功
Promise.all([
  fetchUserData(1),
  fetchUserData(2),
  fetchUserData(3)
])
  .then(users => {
    console.log('所有用户:', users);
  })
  .catch(error => {
    console.error('至少有一个请求失败:', error);
  });

// Promise.race - 返回最先完成的Promise
Promise.race([
  fetchFromServer1(),
  fetchFromServer2()
])
  .then(result => {
    console.log('最快的结果:', result);
  });

// Promise.allSettled - 等待所有Promise完成
Promise.allSettled([
  fetchUserData(1),
  fetchUserData(-1) // 这个会失败
])
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`请求${index}成功:`, result.value);
      } else {
        console.log(`请求${index}失败:`, result.reason);
      }
    });
  });

// Promise.any - 返回第一个成功的Promise
Promise.any([
  fetchFromBackup1(),
  fetchFromBackup2(),
  fetchFromBackup3()
])
  .then(result => {
    console.log('第一个成功的备份:', result);
  })
  .catch(error => {
    console.error('所有备份都失败:', error);
  });

Async/Await语法糖

7. Async/Await基础

Async/Await是Promise的语法糖,让异步代码看起来像同步代码。

// Async函数定义
async function getUserData() {
  try {
    const user = await fetchUserData(1);
    console.log('用户信息:', user);
    
    const posts = await fetchUserPosts(user.id);
    console.log('用户文章:', posts);
    
    return posts;
  } catch (error) {
    console.error('获取数据失败:', error);
    throw error;
  }
}

// 调用Async函数
getUserData()
  .then(posts => console.log('最终结果:', posts))
  .catch(error => console.error('错误:', error));

8. 并行异步操作

使用Promise.all配合Async/Await可以实现并行异步操作。

// 串行执行(慢)
async function sequentialExecution() {
  const user1 = await fetchUserData(1);
  const user2 = await fetchUserData(2);
  const user3 = await fetchUserData(3);
  return [user1, user2, user3];
}

// 并行执行(快)
async function parallelExecution() {
  const [user1, user2, user3] = await Promise.all([
    fetchUserData(1),
    fetchUserData(2),
    fetchUserData(3)
  ]);
  return [user1, user2, user3];
}

// 批量处理
async function batchProcessing(userIds) {
  const batchSize = 5;
  const results = [];
  
  for (let i = 0; i < userIds.length; i += batchSize) {
    const batch = userIds.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(id => fetchUserData(id))
    );
    results.push(...batchResults);
  }
  
  return results;
}

高级异步模式

9. 异步迭代器

异步迭代器可以处理异步的数据流。

// 异步生成器函数
async function* asyncGenerator() {
  let i = 0;
  while (i < 5) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i++;
  }
}

// 使用异步迭代器
async function consumeAsyncGenerator() {
  for await (const value of theAsyncGenerator()) {
    console.log('接收到值:', value);
  }
}

// 实际应用:分页获取数据
async function* fetchPaginatedData(url) {
  let page = 1;
  let hasMore = true;
  
  while (hasMore) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    
    yield data.items;
    
    hasMore = data.hasMore;
    page++;
  }
}

// 使用分页数据
async function processAllData() {
  for await (const items of fetchPaginatedData('/api/data')) {
    items.forEach(item => processItem(item));
  }
}

10. 异步队列

实现一个异步队列,控制并发请求数量。

class AsyncQueue {
  constructor(concurrency = 5) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }
  
  async add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.runNext();
    });
  }
  
  async runNext() {
    if (this.running >= this.concurrency || this.queue.length === 0) {
      return;
    }
    
    this.running++;
    const { task, resolve, reject } = this.queue.shift();
    
    try {
      const result = await task();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      this.running--;
      this.runNext();
    }
  }
}

// 使用异步队列
const queue = new AsyncQueue(3); // 最多3个并发

const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3',
  'https://api.example.com/data4',
  'https://api.example.com/data5'
];

const promises = urls.map(url => 
  queue.add(() => fetch(url).then(res => res.json()))
);

const results = await Promise.all(promises);

11. 异步重试机制

实现自动重试的异步操作。

async function retryAsyncOperation(
  operation,
  maxRetries = 3,
  delay = 1000
) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      
      if (attempt < maxRetries) {
        console.log(`第${attempt}次尝试失败,${delay}ms后重试...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        delay *= 2; // 指数退避
      }
    }
  }
  
  throw new Error(`操作失败,已重试${maxRetries}次: ${lastError.message}`);
}

// 使用示例
async function fetchWithRetry(url) {
  return retryAsyncOperation(
    () => fetch(url).then(res => {
      if (!res.ok) throw new Error('请求失败');
      return res.json();
    }),
    3,
    1000
  );
}

const data = await fetchWithRetry('https://api.example.com/data');

12. 异步超时控制

为异步操作设置超时时间。

function withTimeout(promise, timeoutMs) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('操作超时')), timeoutMs)
    )
  ]);
}

// 使用示例
async function fetchWithTimeout(url, timeout = 5000) {
  try {
    const response = await withTimeout(
      fetch(url),
      timeout
    );
    return response.json();
  } catch (error) {
    if (error.message === '操作超时') {
      console.error('请求超时,请检查网络连接');
    }
    throw error;
  }
}

// AbortController实现可取消的请求
async function fetchWithAbort(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    clearTimeout(timeoutId);
    return response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('请求超时');
    }
    throw error;
  }
}

错误处理最佳实践

13. 全局错误处理

设置全局的异步错误处理器。

// 未捕获的Promise错误
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的Promise拒绝:', event.reason);
  
  // 发送到错误监控服务
  sendToErrorTracking({
    type: 'unhandledRejection',
    error: event.reason,
    stack: event.reason?.stack
  });
});

// 全局错误
window.addEventListener('error', event => {
  console.error('全局错误:', event.error);
  
  sendToErrorTracking({
    type: 'globalError',
    error: event.error,
    message: event.message
  });
});

14. 错误边界模式

在React等框架中实现错误边界。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('组件错误:', error, errorInfo);
    
    // 记录错误
    logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return <ErrorFallback error={this.state.error} />;
    }
    
    return this.props.children;
  }
}

// 使用ErrorBoundary
<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

性能优化技巧

15. 防抖与节流

在异步操作中使用防抖和节流优化性能。

// 异步防抖
function asyncDebounce(func, delay) {
  let timer = null;
  let pendingPromise = null;
  
  return function(...args) {
    return new Promise((resolve, reject) => {
      if (timer) {
        clearTimeout(timer);
        if (pendingPromise) {
          pendingPromise.reject(new Error('取消'));
        }
      }
      
      pendingPromise = { resolve, reject };
      
      timer = setTimeout(async () => {
        try {
          const result = await func.apply(this, args);
          pendingPromise.resolve(result);
        } catch (error) {
          pendingPromise.reject(error);
        }
        timer = null;
        pendingPromise = null;
      }, delay);
    });
  };
}

// 使用示例
const debouncedSearch = asyncDebounce(
  async (query) => {
    const response = await fetch(`/api/search?q=${query}`);
    return response.json();
  },
  300
);

// 搜索输入框
searchInput.addEventListener('input', async (e) => {
  try {
    const results = await debouncedSearch(e.target.value);
    displayResults(results);
  } catch (error) {
    if (error.message !== '取消') {
      console.error('搜索失败:', error);
    }
  }
});

16. 请求缓存

实现异步请求的缓存机制。

class AsyncCache {
  constructor(ttl = 60000) {
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  async get(key, fetcher) {
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    const data = await fetcher();
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
  
  clear() {
    this.cache.clear();
  }
  
  clearExpired() {
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp >= this.ttl) {
        this.cache.delete(key);
      }
    }
  }
}

// 使用示例
const cache = new AsyncCache(300000); // 5分钟缓存

async function getUserInfo(userId) {
  return cache.get(`user:${userId}`, async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  });
}

总结

JavaScript异步编程经历了从回调函数到Promise,再到Async/Await的演进,每种方式都有其适用场景:

1. 选择合适的异步方式

  • 简单场景:回调函数仍然适用
  • 链式操作:Promise提供更好的可读性
  • 复杂逻辑:Async/Await让代码更清晰

2. 最佳实践

  • 错误处理:始终使用try/catch或.catch()
  • 并发控制:合理使用Promise.all和队列
  • 性能优化:使用缓存、防抖、节流等技术
  • 可读性:保持异步代码的清晰和简洁

3. 进阶技巧

  • 异步迭代器:处理异步数据流
  • 重试机制:提高操作可靠性
  • 超时控制:防止操作卡死
  • 错误边界:优雅处理错误

掌握JavaScript异步编程,将帮助你构建出更高效、更可靠的前端应用。记住,异步编程的核心是理解事件循环和Promise的工作原理,这样才能写出真正优秀的异步代码。


本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!

❌
❌