普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月25日掘金 前端

🚀 Vue3 响应式进阶:多层嵌套数据不更新?别让数据"躺平"了!

作者 子兮曰
2026年1月25日 14:15
前言:小明的表单噩梦 想象一下,你是小明,一个刚转前端的后端工程师。老大让你做个复杂的表单页面,用户信息、地址、支付方式,一层套一层。你信心满满地用 reactive 写了个对象,结果发现修改地址字段

🚀JS 为什么能跑这么快?一文把 V8 “翻译官 + 加速器” 机制讲透(AST / 字节码 / JIT / 去优化)

作者 swipe
2026年1月25日 12:05

你的 JS 到底怎么跑起来的?一文看懂 V8:从源码到机器码的“流水线”(含图解)

写下 console.log('hi') 的那一刻,CPU 其实完全看不懂。
真正让 JS “跑起来”的,是 JavaScript 引擎——尤其是 Chrome/Node.js 背后的 V8
这篇文章用一条清晰的流水线,把 V8 的核心机制讲透:Parse → AST → Ignition(字节码) → TurboFan(机器码) → Deopt(去优化回退)


文章推荐:

代码10倍提速!吃透底层架构就是如此简单-腾讯云开发者社区-腾讯云

先建立直觉:V8 是一条“翻译+加速”的流水线

可以把 V8 想象成一个“会学习的翻译官”:

  • 第一目标:让代码尽快跑起来(启动快)
  • 第二目标:把经常跑的代码越跑越快(热点优化)
  • 第三目标:发现假设错了就回退重来(去优化 Deopt)

接下来所有细节,都围绕这三句话展开。


01|为什么 CPU 才是最终执行者

CPU(中央处理器)执行的是机器语言——一串二进制指令。它不认识 JavaScript、也不认识“高级语言”的语法糖。

所以:CPU 是“执行者”,V8 是“翻译官 + 加速器”。

再看一张更直观的图:代码最终一定要落到 CPU 可执行的机器码上。


02|JavaScript 引擎在浏览器里处在什么位置

浏览器内核并不是“只有渲染”,它通常至少包含两大块:

  • 渲染相关:HTML/CSS 解析、布局、绘制
  • 脚本相关:解析并执行 JavaScript

以 WebKit 举例:它可以拆成 WebCoreJavaScriptCore 两部分(JS 引擎就是内核的一部分)。


03|V8 全流程:从源码到机器码

把 V8 的执行流程浓缩成 6 步,会非常清晰:

  1. Parse(解析) :源码 → AST(抽象语法树),并采用 Lazy Parsing(函数即将执行时才完整解析)
  2. Ignition(解释器) :AST → 字节码 Bytecode
  3. 执行字节码:先跑起来,并收集运行信息(类型、分支、调用频率…)
  4. TurboFan(优化编译器) :热点代码 → 优化后的机器码
  5. Deopt(去优化) :假设不成立(常见是类型变化)→ 回退到字节码
  6. 机器码执行:最终交给 CPU

用一张图把这条流水线钉死在脑子里:

同时,AST 长什么样?大概是这种结构化树形表示:


04|Parse 细节:词法分析、语法分析与 AST

很多人卡在“Parse 解析”这一步,原因是:概念名词多,但直觉不够

4.1 词法分析:把代码拆成 token(最小语法单元)

可以理解为“拆词”——把一段 JS 源码拆成一个个最小的记号(token):

  • 关键字 function
  • 标识符 sayHi
  • 运算符 =, +
  • 标点符号 (), {}, ;
  • 字面量 "Hi "

4.2 语法分析:把 token 重新组装成树(AST)

可以理解为“造句”——把 token 按语法规则组装成结构化表达,这棵树就是 AST。

一个好记的口诀:

先词后语:先把“单词”拆出来,再把“语句结构”搭起来。


05|为什么要保留“字节码”这一层

直觉上会觉得:少一层转换就更快,那为什么不直接 AST → 机器码?

因为工程里真正的目标不是“某一步最快”,而是“整体更快、更稳、更可控”。保留字节码主要带来:

  1. 跨平台:字节码不绑定某一种 CPU 指令集
  2. 优化更聪明:先跑字节码,收集运行数据,再决定怎么生成更优机器码
  3. 更安全、更可控:更容易做隔离、策略、内存管理
  4. 更容易调试:断点/单步在字节码层更容易实现

配合这张图理解,会很顺:


06|架构拆解:Parse / Ignition / TurboFan 各做什么

用“岗位职责”来记:

  • Parse:把 JS 代码变成 AST(解释器不直接认识 JS 源码)
  • Ignition:把 AST 变成字节码并执行,同时收集 TurboFan 需要的运行信息(比如类型信息)
  • TurboFan:把热点字节码编译成更快的机器码(并持续迭代优化)

这里有一个非常关键的运行规律:

热点函数会被优化,但类型变化等情况会触发去优化回退


07|预解析 vs 全量解析:Lazy Parsing 为什么能让启动更快

V8 并不会“上来就把一切都解析得巨细无遗”,它会做取舍:

7.1 预解析(Pre-parsing)

  • 目标:快速扫描,提取结构信息(变量/函数声明等)
  • 特点:不深挖函数体内部逻辑 → 更快

7.2 全量解析(Full parsing)

  • 目标:把函数体、表达式、语句细节全部建出来
  • 特点:AST 更完整 → 便于后续生成字节码与优化

因此,“函数没执行会不会生成 AST?”更准确的回答是:

  • 会生成一个简化的结构架子(预解析)
  • 真要执行之前,会补齐为完整 AST(全量解析)

08|走一遍官方图:token、AST、字节码到底怎么来的

先准备一段模板代码:

name = "XiaoWu"
console.log(name)

function sayHi(name) {
  console.log("Hi " + name)
}

sayHi(name)

8.1 官方流程图:从输入到字节码

这张图非常经典,建议收藏:

按图理解就是:

  • Scanner:扫描字符流 → 生成 tokens
  • PreParser:做预解析(快速判断结构)
  • Parser:构建 AST
  • Bytecode:AST → 字节码

8.2 token 长什么样(词法分析结果)

下面是典型 token 形态(摘取关键类型,方便理解):

Token(type='Keyword', value='const')            // 关键字
Token(type='Identifier', value='name')          // 标识符
Token(type='Operator', value='=')               // 运算符
Token(type='StringLiteral', value='"coderwhy and XiaoYu"') // 字符串字面量
Token(type='Punctuation', value=';')            // 标点符号

Token(type='Identifier', value='console')
Token(type='Punctuation', value='.')
Token(type='Identifier', value='log')
Token(type='Punctuation', value='(')
Token(type='Identifier', value='name')
Token(type='Punctuation', value=')')
Token(type='Punctuation', value=';')

8.3 语法分析:预解析如何参与

这张图专门解释“预解析/解析”的关系:


09|热点优化与去优化:为什么“有时突然变慢”

V8 会把被频繁执行的函数标记为 热点函数,然后交给 TurboFan 编译为更快的机器码。

但注意:优化是有前提假设的。最常见的假设就是“类型稳定”。

来看这个例子:

function sum (num1,num2){
  return num1 + num2
}

// 多次调用 -> 可能成为热点函数 -> 被优化
sum(20,20)
sum(20,20)

// 类型突然变化 -> 之前的机器码假设不成立 -> 去优化回退
sum('xiaoyu','coderwhy')

发生了什么?

  • 前两次传入 number,优化器可能会假设“这里一直是 number 加法”
  • 第三次突然变成 string 拼接,机器码可能无法正确处理 → 回退到字节码重新收集信息,再决定是否重新优化

这就是性能“抖一下”的根源之一:Deopt(去优化)


10|字节码与机器码(了解即可):JIT 到底做了什么

机器码的生成通常依赖 JIT(Just-In-Time Compilation,即时编译)

  • 把字节码转换成本地机器码
  • 把结果缓存起来
  • 后续执行直接复用缓存的机器码(更快)

TurboFan 作为优化编译器,会基于 IR(中间表示)做多层优化(类型、内联、控制流等):

同时,字节码到机器码的过程中,会存在不同优化策略:

这里还有两张配图(保持原样保留):


结尾:把知识用起来

理解 V8 的意义,不是为了背名词,而是为了形成“性能直觉”:

  • 让热点函数更容易被优化:参数类型尽量稳定
  • 减少去优化回退:避免同一段热点路径里频繁出现类型漂移
  • 理解启动性能:Lazy Parsing 的策略决定了“先跑起来”的快慢

1. Vue3必学:defineAsyncComponent四大配置全攻略,组件懒加载秒上手

作者 boooooooom
2026年1月24日 23:41

在 Vue 3 中,defineAsyncComponent 是实现组件懒加载的核心 API,它能帮助我们按需加载组件、优化应用首屏加载速度,尤其适用于大型应用中组件数量多、体积大的场景。本文将从基础用法入手,详细拆解其 loading、error、delay、timeout 四大配置的功能与实践,帮你彻底掌握组件异步加载的精髓。

一、defineAsyncComponent 基础用法

组件懒加载的核心逻辑是“在需要时才加载组件代码”,而非应用初始化时一次性加载所有组件。Vue 3 提供了 defineAsyncComponent 方法封装异步组件,支持两种基础用法:简单语法和完整配置语法。

1. 简单语法(仅指定加载函数)

最简洁的用法是传入一个返回 Promise 的加载函数,该函数内部通过动态 import 加载组件。当组件被渲染时,会自动执行加载函数,加载完成后渲染组件。


// 引入 defineAsyncComponent
import { defineAsyncComponent } from 'vue'

// 定义异步组件(简单语法)
const AsyncDemo = defineAsyncComponent(() => 
  // 动态 import 加载组件,返回 Promise
  import('./components/AsyncDemo.vue')
)

// 在组件中正常使用
export default {
  components: {
    AsyncDemo
  }
}

注意:动态 import() 是 ES 语法,会返回一个 Promise 对象,Vue 内部会自动处理 Promise 的成功与失败状态。

2. 完整配置语法(支持加载/错误状态等配置)

当需要自定义加载状态、错误处理、加载延迟等场景时,可传入一个配置对象,这也是实际开发中更常用的方式。完整配置包含 loaderloadingComponenterrorComponentdelaytimeout 等属性,后续将逐一详解。


const AsyncDemo = defineAsyncComponent({
  // 加载函数(必选),同简单语法的加载函数
  loader: () => import('./components/AsyncDemo.vue'),
  // 加载中显示的组件
  loadingComponent: Loading,
  // 加载失败显示的组件
  errorComponent: Error,
  // 延迟显示加载组件的时间(毫秒)
  delay: 200,
  // 加载超时时间(毫秒)
  timeout: 3000,
  // 其他可选配置...
})

二、四大核心配置详解

下面针对完整配置中的四大核心属性(loading/error/delay/timeout),结合场景与实例逐一拆解,说明其作用、用法及注意事项。

1. loadingComponent:加载中状态组件

当异步组件正在加载时,Vue 会渲染 loadingComponent 指定的组件,用于提示用户“加载中”(如骨架屏、加载动画等)。

使用要点:

  • loadingComponent 需是一个已定义的 Vue 组件,可全局注册或局部引入。
  • 加载成功后,加载组件会自动被替换为目标异步组件。
  • 若加载时间极短(如小于 delay 配置的时间),加载组件可能不会显示,避免频繁切换导致的闪烁。

实例:


// 引入加载组件和错误组件
import Loading from './components/Loading.vue'
import Error from './components/Error.vue'

const AsyncDemo = defineAsyncComponent({
  loader: () => import('./components/AsyncDemo.vue'),
  // 加载中显示 Loading 组件
  loadingComponent: Loading,
  // 加载失败显示 Error 组件
  errorComponent: Error
})

Loading.vue 示例(简单加载动画):


<template>
  <div class="loading">
    <span>加载中...</span>
  </div>
</template>

<style scoped>
.loading {
  text-align: center;
  padding: 20px;
  color: #666;
}
</style>

2. errorComponent:加载失败状态组件

当异步组件加载失败(如网络错误、组件路径错误)时,Vue 会渲染 errorComponent 指定的组件,用于提示用户加载失败,并可提供重试等交互。

使用要点:

  • 加载失败的原因包括:网络中断、动态 import 路径错误、组件内部报错等。
  • Vue 会向 errorComponent 传递一个 error 属性,包含错误信息,可在组件中使用。
  • 可在错误组件中提供“重试加载”按钮,通过调用 error.retry() 重新触发加载函数;调用 error.fail() 标记加载失败(不再重试)。

实例(带重试功能的错误组件):


// Error.vue
<template>
  <div class="error">
    <p>组件加载失败:{{ error.message }}</p>
    <button @click="error.retry()">重试加载</button>
    <button @click="error.fail()">确认失败</button>
  </div>
</template>

<script setup>
// 接收 Vue 传递的 error 属性
const props = defineProps({
  error: {
    type: Object,
    required: true
  }
})
</script>

<style scoped>
.error {
  text-align: center;
  padding: 20px;
  color: #ff4d4f;
}
button {
  margin: 0 8px;
  padding: 4px 12px;
}
</style>

3. delay:延迟显示加载组件的时间

delay 用于设置“延迟多久后显示加载组件”,单位为毫秒(默认值为 200)。其核心作用是避免“加载组件闪烁”——若组件加载速度极快(如本地资源、缓存资源),加载组件仅显示几毫秒就消失,会给用户带来不良体验。

逻辑说明:

  • 若组件加载时间 ≤ delay:不显示加载组件,直接渲染目标组件。
  • 若组件加载时间 > delay:从加载开始经过 delay 毫秒后,显示加载组件,直到加载完成或失败。

实例:


const AsyncDemo = defineAsyncComponent({
  loader: () => import('./components/AsyncDemo.vue'),
  loadingComponent: Loading,
  errorComponent: Error,
  // 延迟 300 毫秒显示加载组件,避免快速加载时的闪烁
  delay: 300
})

4. timeout:加载超时时间

timeout 用于设置组件加载的超时时间,单位为毫秒(默认无超时限制)。若加载时间超过设定值,Vue 会判定为加载失败,渲染 errorComponent

使用要点:

  • 若网络环境较差,建议设置合理的超时时间(如 5000 毫秒),避免用户长时间等待无反馈。
  • 超时后触发的错误,可通过错误组件的 error.retry() 重试加载。
  • 若需禁用超时限制,可设置 timeout: Infinity

实例:


const AsyncDemo = defineAsyncComponent({
  loader: () => import('./components/AsyncDemo.vue'),
  loadingComponent: Loading,
  errorComponent: Error,
  delay: 300,
  // 加载超时时间设为 5 秒,超过则显示错误组件
  timeout: 5000
})

三、进阶用法与注意事项

1. 结合 Suspense 使用

Vue 3 的 Suspense 组件可与 defineAsyncComponent 配合,实现更灵活的异步组件控制。Suspense 提供 <template #default>(异步组件成功渲染内容)和 <template #fallback>(加载中内容),此时可省略 loadingComponent配置。


<template>
  <Suspense>
    <template #default>
      <AsyncDemo />
    </template>
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

<script setup>
import { defineAsyncComponent, Suspense } from 'vue'
const AsyncDemo = defineAsyncComponent(() => import('./components/AsyncDemo.vue'))
</script>

2. 动态控制加载函数

加载函数可根据条件动态返回不同的组件,实现“按需加载不同组件”的场景(如根据用户权限加载不同组件)。


const AsyncComponent = defineAsyncComponent(() => {
  // 根据权限动态加载组件
  if (userRole === 'admin') {
    return import('./components/AdminComponent.vue')
  } else {
    return import('./components/UserComponent.vue')
  }
})

3. 注意事项

  • 异步组件不能直接在 <script setup> 中通过 import 引入后立即使用,需通过defineAsyncComponent 封装。
  • 加载函数返回的 Promise 若被 reject,会触发加载失败,渲染错误组件。
  • 生产环境中,动态 import() 会被打包工具(如 Vite、Webpack)分割为独立的代码块,实现真正的按需加载。

四、总结

defineAsyncComponent 是 Vue 3 优化应用性能的重要工具,通过基础加载函数实现组件懒加载,再结合 loading、error、delay、timeout 四大配置,可覆盖绝大多数异步组件的使用场景:loading 组件提升用户等待体验,error 组件处理加载异常,delay 避免组件闪烁,timeout 防止无限等待。

在实际开发中,建议根据组件的体积、加载场景(如首屏、弹窗)合理配置参数,结合 Suspense 组件实现更灵活的异步控制,让应用加载更快、体验更优。

Next.js第二十三章(Next配置文件)

作者 小满zs
2026年1月25日 06:11

next.config.js配置

本章会讲解Next.js的配置文件next.config.js的配置项。注:本章不会讲所有的配置项,只会讲使用率50%的配置项,以及项目中真实使用的配置项。

查看完整版配置项观看: next.config.js配置

根据不同环境进行配置

例如我想在开发环境配置 XXX,或者生产环境配置YYY,那么我们可以使用next/constants来判断当前环境。

//Next.js next/constants内置的常量
export declare const PHASE_EXPORT = "phase-export"; // 导出静态站点
export declare const PHASE_PRODUCTION_BUILD = "phase-production-build"; // 生产环境构建
export declare const PHASE_PRODUCTION_SERVER = "phase-production-server"; // 生产环境服务器
export declare const PHASE_DEVELOPMENT_SERVER = "phase-development-server"; // 开发环境服务器
export declare const PHASE_TEST = "phase-test"; // 测试环境
export declare const PHASE_INFO = "phase-info"; // 信息

我们要根据不同环境配置,需要返回一个函数,而不是直接返回一个对象,在函数中会接受一个参数phase,这个参数是Next.js的环境,我们可以根据这个参数来判断当前环境。

//next.config.ts
import { PHASE_DEVELOPMENT_SERVER, PHASE_TYPE } from 'next/constants'
import type { NextConfig } from 'next'

export default (phase: PHASE_TYPE): NextConfig => {
  const nextConfig: NextConfig = {
     reactCompiler: false,
  }

  if (phase === PHASE_DEVELOPMENT_SERVER) {
    nextConfig.reactCompiler = true // 开发环境使用reactCompiler
  }

  //if() 其他环境.....

  return nextConfig
}

Next.js配置端口号

这是Next.js很迷的一个操作,通过一般脚手架或者其他项目都会在配置文件进行配置端口号,但是Next.js却没有,而是在启动命令中进行配置。(默认是3000端口)

  "scripts": {
    "dev": "next dev -p 8888", // 开发环境端口号
    "build": "next build",
    "start": "next start -p 9999 " // 生产环境端口号
  },

Next.js导出静态站点

需要在next.config.js文件中配置outputexport,表示导出静态站点。distDir表示导出目录,默认为out

具体用法请查看: 静态导出SSG

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  /* config options here */
  output: "export", // 导出静态站点
  distDir: "dist", // 导出目录
  trailingSlash: true, // 添加尾部斜杠,生成 /about/index.html 而不是 /about.html
};

export default nextConfig;

Next.js配置图片优化

Next.jsImage组件默认只允许加载本地图片,如果需要加载远程图片,需要配置next.config.js文件。

详细用法请查看: 图片优化

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  images: {
    remotePatterns: [
      {
        protocol: 'https', // 协议
        hostname: 'eo-img.521799.xyz', // 主机名
        pathname: '/i/pc/**', // 路径
        port: '', // 端口
      },
    ],
    formats: ['image/avif', 'image/webp'], //默认是 ['image/webp']
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], // 设备尺寸
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // 图片尺寸
  },
};

自定义响应标头

例如配置CORS跨域,或者是自定义响应标头等,只要是http支持的响应头都可以配置。

HTTP响应头参考: HTTP响应头

  const nextConfig: NextConfig = {
     headers: () => {
      return [
         {
          source: '/:path*', // 匹配路径 所有路径 也支持精准匹配 例如/api/user 包括支持动态路由等 /api/user/:id 
          headers: [
            {
              key: 'Access-Control-Allow-Origin', //允许跨域
              value: '*' // 允许所有域名访问
            },
            {
              key: 'Access-Control-Allow-Methods', //允许的请求方法
              value: 'GET, POST, PUT, DELETE, OPTIONS' // 允许的请求方法
            },
            {
              key: 'Access-Control-Allow-Headers', //允许的请求头
              value: 'Content-Type, Authorization' // 允许的请求头
            }
          ]
         },
         {
          source: '/home', // 精准匹配 /home 路径
          headers: [
            {
              key: 'X-Custom-Header', //自定义响应头
              value: '123456' // 值
            },
          ]
         }
      ]
    }
  }

assetsPrefix配置

assetsPrefix配置用于配置静态资源前缀,例如:部署到CDN后,静态资源路径会发生变化,需要配置这个配置项。

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  /* config options here */
  assetsPrefix: 'https://cdn.example.com', // 静态资源前缀
};

export default nextConfig;

未配置assetsPrefix时:

/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js

配置assetsPrefix后:

https://cdn.example.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js

basePath配置

应用前缀:也就是跳转路径中增加前缀,例如前缀是/docs,那么跳转/home就需要跳转到/docs/home。访问根目录也需要增加前缀,例如访问/就需要跳转到/docs。这儿可以使用重定向来实现。访问/自动跳转到/docs

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  /* config options here */
  basePath: '/docs', // 基础路径
  redirects() {
    return [
      {
        source: '/', // 源路径
        destination: '/docs', // 目标路径
        basePath: false, // 是否使用basePath 默认情况下 source 和 destination 都会自动加上 basePath 前缀 就变成了/docs/docs 所以这儿不需要增加
        permanent: false, // 是否永久重定向
      },
    ]
  },
};

export default nextConfig;

如果使用link跳转的话,无需增加basePath前缀,因为Link组件会自动增加basePath前缀。 当他跳转/home时,会自动跳转到/docs/home

import Link from 'next/link'
export default function Page() {
  return (<div>
    <h1>小满zs Page</h1>
    <Link href="/home">Home</Link>
  </div>)
}

compress

compress配置用于配置压缩,例如:压缩js/css/html等。默认情况是开启的,如果需要关闭,可以配置为false。

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  compress: true, // 压缩
};

export default nextConfig;

日志配置

日志配置用于配置日志,例如:显示完整的URL等。

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  logging:{
    fetches: {
      fullUrl: true, // 显示完整的URL
    },
  }
};

export default nextConfig;

页面扩展

默认情况下,Next.js 接受以下扩展名的文件:.tsx.js、 .js .ts、.jsx.md、.js.js。可以修改此设置以允许其他扩展名,例如 markdown(.md.md、.md .mdx)。

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
};

export default nextConfig;

devIndicators

关闭调试指示器,默认情况下是开启的,如果需要关闭,可以配置为false。

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  devIndicators: false, // 关闭开发指示器
  // devIndicators:{
  //   position:'bottom-right', //也支持放入其他位置 bottom-right bottom-left top-right top-left
  // },
};

export default nextConfig;
image.png

generateEtags

Next.js会为静态文件生成ETag,用于缓存控制。默认情况下是开启的,如果需要关闭,可以配置为false。

浏览器会根据ETag来判断文件是否发生变化,如果发生变化,则重新下载文件。

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  generateEtags: false, // 关闭生成ETag 默认开启
};

export default nextConfig;

turbopack

Next.js已内置turbopack进行打包编译等操作,所以允许透传配置项给turbopack。

一般情况下是不需要做太多优化的,因为它都内置了例如tree-shaking压缩 按需编译 语法降级 等优化。

具体用法请查看: turbopack

例如我们需要编译其他文件less配置如下:

npm i less-loader -D
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
    turbopack:{
     rules:{
          '*.less':{
          loaders:['less-loader'],
          as:'*.css',
        }
     }
  }
}
export default nextConfig;
昨天 — 2026年1月24日掘金 前端

1.8GB 内存也能跑大模型!Ollama Docker 部署完整指南

2026年1月24日 21:05

想在服务器上部署私有 AI 模型,但内存不够用?本文教你用 Docker + Swap 优化,让低配服务器也能流畅运行 Ollama 大模型。

背景

为什么选择 Docker 部署?

因为直接使用命令会报错,无法运行ollama。

image.png

1. 简介

1.1 为什么使用 Docker 部署?

优势 说明
环境隔离 不污染宿主机环境,依赖问题少
一键部署 容器化部署,跨平台一致性好
易于管理 重启、更新、迁移方便
资源控制 可限制内存、CPU 使用
适合生产 稳定可靠,推荐生产环境使用

1.2 硬件要求

模型规模 内存要求 推荐配置
0.5B-3B 2-4GB 最低 2GB 可用内存
7B-14B 8-16GB 最低 8GB 可用内存
30B+ 32GB+ 最低 32GB 可用内存

1.3 低配服务器(<2GB 内存)

如果你的服务器内存不足(如 1GB-2GB),运行大模型会遇到以下错误:

Error: 500 Internal Server Error: llama runner process has terminated: signal: killed

什么是 Swap?

Swap 是 Linux 系统中的一块硬盘空间,当作"备用内存"使用。当物理内存(RAM)不够用时,系统会把暂时不用的数据从内存搬到 Swap 中,腾出物理内存给需要运行的程序。

┌─────────────────────────────────────────────────┐
│  物理内存 (RAM)     =  你的办公桌(快速但小)    │
│  Swap (虚拟内存)    =  旁边的储物柜(慢但大)    │
│                                                 │
│  当办公桌放满东西时:                            │
│  把不常用的文件 → 放到储物柜 (Swap)             │
│  腾出空间 → 放置正在处理的文件                  │
└─────────────────────────────────────────────────┘

Swap 的作用

作用 说明
防止系统崩溃 内存不足时,用 Swap 补充,避免进程被杀死
运行大程序 允许运行超出物理内存的程序(如大语言模型)
内存回收 把不活跃的内存页面移到 Swap,释放物理内存

为什么需要 Swap?

你的服务器配置:
- 物理内存:1.8GB
- 想运行:3b 模型(需要 ~4GB 内存)

没有 Swap:
1.8GB < 4GB → 程序被杀死 ❌

有 5GB Swap:
1.8GB + 5GB = 6.8GB > 4GB → 可以运行 ✅

注意:使用 Swap 会牺牲性能(硬盘速度约为内存的 1/100),但总比程序崩溃好。

添加 Swap 虚拟内存

# 创建 4GB swap 文件
dd if=/dev/zero of=/swapfile bs=1M count=4096
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# 永久生效
echo '/swapfile none swap sw 0 0' >> /etc/fstab

# 验证
free -h

不同内存配置的模型推荐

服务器内存 推荐模型 Swap 需求
1GB qwen2.5-coder:0.5b 建议 2GB
2GB qwen2.5-coder:0.5b / 1.5b 建议 3GB
4GB qwen2.5-coder:3b 不需要
8GB+ qwen2.5-coder:7b 不需要

Swap 性能判断

Swap 使用量 状态 建议
0-500MB 正常 无需处理
500MB-1GB 一般 注意性能
1GB-2GB 较慢 考虑换小模型
>2GB 很慢 必须换小模型

内存监控命令

# 查看当前内存和 Swap 状态
free -h

# 实时监控内存(每 1 秒刷新)
watch -n 1 free -h

# 查看 Docker 容器资源使用
docker stats ollama

# 查看容器内存限制
docker inspect ollama | grep -i memory

# 查看系统内存配置
cat /proc/sys/vm/overcommit_memory
# 0 = 启发式过度分配(默认)
# 1 = 始终允许过度分配
# 2 = 严格控制,不允许过度分配

运行模型时实时监控

开启两个终端窗口:

终端 1:运行模型

docker exec -it ollama ollama run qwen2.5-coder:0.5b

终端 2:实时监控

watch -n 1 'free -h && echo "---" && docker stats ollama --no-stream'

常见问题排查

问题:模型运行时被杀死

# 1. 检查容器内存限制
docker inspect ollama | grep -i memory

# 2. 如果有内存限制,重新创建容器
docker rm -f ollama
docker run -d \
  -p 11434:11434 \
  --name ollama \
  --restart always \
  --memory-swap=-1 \
  ollama/ollama:latest

# 3. 启用内存过度分配
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
echo 'vm.overcommit_memory = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# 4. 重启容器
docker restart ollama

问题:Swap 使用过高导致卡顿

# 查看当前 Swap 使用
free -h

# 如果 Swap 使用 > 1GB,建议切换到更小的模型
docker exec -it ollama ollama run qwen2.5-coder:0.5b

2. 安装 Docker

2.1 Ubuntu/Debian

# 一键安装 Docker
curl -fsSL https://get.docker.com | sh

# 将当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER

# 重新登录或执行以下命令使组权限生效
newgrp docker

# 验证安装
docker --version

2.2 CentOS/RHEL

# 安装 Docker
sudo yum install -y docker

# 启动 Docker 服务
sudo systemctl start docker
sudo systemctl enable docker

# 将当前用户加入 docker 组
sudo usermod -aG docker $user

# 验证安装
docker --version

2.3 验证 Docker 安装

# 运行测试容器
docker run hello-world

# 查看 Docker 版本
docker --version
docker info

3. 部署 Ollama 容器

3.1 拉取镜像

# 拉取最新版 Ollama 镜像
docker pull ollama/ollama:latest

# 或指定版本
docker pull ollama/ollama:0.5.7

3.2 启动容器

CPU 模式(默认):

docker run -d \
  -p 11434:11434 \
  --name ollama \
  --restart always \
  ollama/ollama:latest

GPU 模式(需要 NVIDIA GPU):

# 首先安装 NVIDIA Container Toolkit
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
  sudo tee /etc/apt/sources.list.d/nvidia-docker.list

sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker

# 启动带 GPU 的容器
docker run -d \
  --gpus all \
  -p 11434:11434 \
  --name ollama \
  --restart always \
  ollama/ollama:latest

3.3 验证容器运行

# 查看容器状态
docker ps

# 查看容器日志
docker logs -f ollama

# 测试 API
curl http://localhost:11434/api/tags

4. 模型管理

4.1 拉取模型

# 拉取 qwen2.5-coder:3b
docker exec -it ollama ollama pull qwen2.5-coder:3b

# 拉取其他模型
docker exec -it ollama ollama pull qwen2.5:7b
docker exec -it ollama ollama pull deepseek-r1:7b

4.2 查看已安装模型

docker exec -it ollama ollama list

4.3 运行模型(交互式)

docker exec -it ollama ollama run qwen2.5-coder:3b

4.4 删除模型

docker exec -it ollama ollama rm qwen2.5-coder:3b

4.5 推荐模型

模型 用途 内存需求
qwen2.5-coder:0.5b 代码生成(轻量) ~1GB
qwen2.5-coder:3b 代码生成(推荐) ~4GB
qwen2.5-coder:7b 代码生成(专业) ~8GB
qwen2.5:3b 通用对话 ~4GB
qwen2.5:7b 通用对话(推荐) ~8GB

5. API 调用

5.1 基础调用格式

# 生成文本
curl http://localhost:11434/api/generate -d '{
  "model": "qwen2.5-coder:3b",
  "prompt": "用python写一个快速排序",
  "stream": false
}'

# 对话模式
curl http://localhost:11434/api/chat -d '{
  "model": "qwen2.5-coder:3b",
  "messages": [
    {"role": "user", "content": "你好"}
  ],
  "stream": false
}'

5.2 参数说明

参数 类型 说明 默认值
model string 模型名称 -
prompt string 输入文本 -
stream boolean 是否流式输出 true
temperature number 温度(0-1),越高越随机 0.8
num_ctx number 上下文长度 2048

5.3 Python 调用示例

import requests

API_URL = "http://localhost:11434/api/generate"

def call_ollama(prompt: str, model: str = "qwen2.5-coder:3b"):
    response = requests.post(API_URL, json={
        "model": model,
        "prompt": prompt,
        "stream": False
    })
    return response.json()["response"]

# 使用
result = call_ollama("用python写一个快速排序")
print(result)

5.4 JavaScript 调用示例

浏览器环境(原生 Fetch)

// 非流式响应
async function callOllama(prompt) {
  const response = await fetch("http://localhost:11434/api/generate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "qwen2.5-coder:3b",
      prompt: prompt,
      stream: false
    })
  });

  const data = await response.json();
  return data.response;
}

// 使用
callOllama("用python写一个快速排序").then(console.log);

流式响应(浏览器)

async function chatWithOllama(prompt) {
  const response = await fetch("http://localhost:11434/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "qwen2.5-coder:3b",
      messages: [{ role: "user", content: prompt }],
      stream: true
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let result = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split("\n").filter(line => line.trim());

    for (const line of lines) {
      if (line.startsWith("data: ")) {
        const data = line.slice(6);
        if (data === "[DONE]") continue;
        try {
          const json = JSON.parse(data);
          const content = json.choices?.[0]?.delta?.content;
          if (content) {
            result += content;
            console.log(content);  // 实时输出
          }
        } catch (e) {
          // 忽略解析错误
        }
      }
    }
  }
  return result;
}

// 使用
chatWithOllama("用python写一个快速排序");

Node.js 环境

const axios = require("axios");

async function callOllama(prompt) {
  const response = await axios.post(
    "http://localhost:11434/api/generate",
    {
      model: "qwen2.5-coder:3b",
      prompt: prompt,
      stream: false
    }
  );

  return response.data.response;
}

// 使用
callOllama("用python写一个快速排序").then(console.log);

带认证的调用

// 如果设置了 API 密钥
async function callOllamaWithAuth(prompt) {
  const response = await fetch("http://localhost:11434/api/generate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer your_api_key_here"
    },
    body: JSON.stringify({
      model: "qwen2.5-coder:3b",
      prompt: prompt,
      stream: false
    })
  });

  const data = await response.json();
  return data.response;
}

5.5 OpenAI 兼容格式(JavaScript)

// 使用 OpenAI SDK 调用 Ollama
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: "http://localhost:11434/v1",
  apiKey: "ollama"  // 不需要真实 key
});

async function chat(prompt) {
  const response = await client.chat.completions.create({
    model: "qwen2.5-coder:3b",
    messages: [{ role: "user", content: prompt }]
  });

  return response.choices[0].message.content;
}

// 使用
chat("用python写一个快速排序").then(console.log);

5.6 外网调用示例

// 如果配置了外网访问(需要 HTTPS + API Key)
async function callOllamaRemote(prompt) {
  const response = await fetch("https://your-domain.com/api/generate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer your_secure_password"
    },
    body: JSON.stringify({
      model: "qwen2.5-coder:3b",
      prompt: prompt,
      stream: false
    })
  });

  const data = await response.json();
  return data.response;
}

6. 容器管理

6.1 查看容器状态

# 查看运行中的容器
docker ps

# 查看所有容器(包括停止的)
docker ps -a

# 查看容器详细信息
docker inspect ollama

6.2 日志管理

# 查看实时日志
docker logs -f ollama

# 查看最近 100 行日志
docker logs --tail 100 ollama

# 查看带时间戳的日志
docker logs -t ollama

6.3 启停重启

# 停止容器
docker stop ollama

# 启动容器
docker start ollama

# 重启容器
docker restart ollama

# 删除容器(需先停止)
docker rm -f ollama

6.4 进入容器

# 进入容器 shell
docker exec -it ollama bash

# 在容器中执行命令
docker exec -it ollama ollama list

7. 进阶配置

7.1 持久化模型存储

默认情况下,模型存储在容器内部,删除容器后模型会丢失。使用挂载卷持久化:

# 删除旧容器
docker rm -f ollama

# 重新创建,挂载本地目录
docker run -d \
  -p 11434:11434 \
  -v ollama_data:/root/.ollama \
  --name ollama \
  --restart always \
  ollama/ollama:latest

7.2 资源限制

# 限制内存使用为 4GB
docker run -d \
  -p 11434:11434 \
  --memory=4g \
  --name ollama \
  --restart always \
  ollama/ollama:latest

# 限制 CPU 使用
docker run -d \
  -p 11434:11434 \
  --cpus=2.0 \
  --name ollama \
  --restart always \
  ollama/ollama:latest

7.3 环境变量配置

docker run -d \
  -p 11434:11434 \
  -e OLLAMA_HOST=0.0.0.0:11434 \
  -e OLLAMA_NUM_PARALLEL=4 \
  -e OLLAMA_DEBUG=0 \
  -v ollama_data:/root/.ollama \
  --name ollama \
  --restart always \
  ollama/ollama:latest

7.4 使用 Docker Compose

创建 docker-compose.yml

version: '3.8'

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0:11434
      - OLLAMA_NUM_PARALLEL=4
    restart: always
    # GPU 配置(需要 nvidia-docker)
    # deploy:
    #   resources:
    #     reservations:
    #       devices:
    #         - driver: nvidia
    #           count: all
    #           capabilities: [gpu]

volumes:
  ollama_data:

启动:

docker-compose up -d

7.5 国内镜像加速

# 使用国内镜像源
docker pull registry.cn-hangzhou.aliyuncs.com/ollama/ollama:latest

# 或使用代理
docker pull ollama/ollama:latest

8. 故障排查

8.1 容器启动失败

# 查看容器日志
docker logs ollama

# 常见错误:GPU 配置问题
# 解决方案:删除容器,使用 CPU 模式重新创建
docker rm -f ollama
docker run -d -p 11434:11434 --name ollama --restart always ollama/ollama:latest

8.2 无法访问 API

# 检查容器是否运行
docker ps

# 检查端口是否正确映射
docker port ollama

# 测试容器内部 API
docker exec ollama curl http://localhost:11434/api/tags

# 检查防火墙
sudo ufw status  # Ubuntu
sudo firewall-cmd --list-all  # CentOS

8.3 模型加载慢

# 查看资源使用情况
docker stats ollama

# 检查磁盘 IO
docker exec ollama df -h

8.4 内存不足

# 查看容器资源使用
docker stats --no-stream

# 使用更小的模型
docker exec -it ollama ollama pull qwen2.5-coder:0.5b

# 或限制容器内存
docker update --memory=4g ollama

9. 生产部署建议

9.1 安全配置

# 绑定到本地地址
docker run -d \
  -p 127.0.0.1:11434:11434 \
  --name ollama \
  ollama/ollama:latest

# 使用反向代理(Nginx)配置 HTTPS

9.2 监控配置

# 使用 Prometheus + Grafana 监控
docker run -d \
  --name prometheus \
  -p 9090:9090 \
  prom/prometheus

# 配置 cAdvisor 监控容器
docker run -d \
  --name cadvisor \
  -p 8080:8080 \
  google/cadvisor:latest

9.3 高可用配置

# 使用负载均衡
# 部署多个 Ollama 实例,通过 Nginx 负载均衡

# 使用健康检查
docker run -d \
  --name ollama \
  --health-cmd="curl -f http://localhost:11434/api/tags || exit 1" \
  --health-interval=30s \
  --health-timeout=10s \
  --health-retries=3 \
  ollama/ollama:latest

10. 常用命令速查

# 拉取模型
docker exec -it ollama ollama pull qwen2.5-coder:3b

# 查看模型列表
docker exec -it ollama ollama list

# 运行模型
docker exec -it ollama ollama run qwen2.5-coder:3b

# 查看日志
docker logs -f ollama

# 重启容器
docker restart ollama

# 进入容器
docker exec -it ollama bash

# 删除容器
docker rm -f ollama

# 测试 API
curl http://localhost:11434/api/tags

JSyncQueue——一个开箱即用的鸿蒙异步任务同步队列

作者 江澎涌
2026年1月24日 18:52
一、简介 在鸿蒙应用开发中,异步任务的顺序执行是一个常见需求。当多个异步任务需要按照特定顺序执行时,如果不加控制,可能会导致执行顺序混乱。 JSyncQueue 提供了一个简洁的解决方案: 顺序执行保
❌
❌