普通视图

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

🚀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 的策略决定了“先跑起来”的快慢

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日掘金 前端

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

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