脚本语言是怎么跑起来的?一文看懂运行原理

你写完一段 Python 或 JavaScript 代码,双击运行,它就动起来了——但背后到底发生了什么?脚本语言不像 C 程序那样编译成机器码直接执行,它的运行过程更像一场‘边读边演’的即兴演出。

不是编译,也不是纯解释

很多人以为脚本语言就是‘逐行解释执行’,其实现代主流脚本语言(比如 Python、Node.js、PHP)走的是‘先编译成中间字节码,再由虚拟机执行’的路子。以 Python 为例:当你运行 python hello.py,Python 解释器会先把源码编译成 .pyc 字节码(存在 __pycache__ 文件夹里),再交给 Python 虚拟机(PVM)一条条执行。这个过程既不是传统意义上的编译,也不是纯粹的解释,而是一种混合模式。

JavaScript 的 V8 引擎更激进

Chrome 和 Node.js 用的 V8 引擎,干脆跳过了字节码这一步。它先把 JS 源码解析成抽象语法树(AST),再生成机器码——而且是边运行边优化:刚开始用简单快速的编译器生成基础代码,等某段函数被反复调用,就换成优化编译器重编译,把 for (let i = 0; i < arr.length; i++) 这类常见模式直接‘记住’并加速。这就是为什么同一个 JS 函数,第一次跑慢,后面越来越快。

举个实际例子

假设你写了这样一段 Python:

def add(a, b):<br>    return a + b<br><br>print(add(3, 5))

运行时,Python 先把它变成字节码指令(可用 import dis; dis.dis(add) 查看),类似:
LOAD_FAST    0 (a)
LOAD_FAST    1 (b)
BINARY_ADD
RETURN_VALUE

这些指令不是 CPU 能直接跑的,而是由 PVM 识别并调度底层 C 函数去完成加法、返回等动作。

为什么脚本语言启动快,但长期运行可能不如 C?

因为少了‘提前全量编译+链接’这一步,加载即跑,省了时间;但代价是每次执行都要经过词法分析、语法解析、字节码生成(或 JIT 编译)、内存管理等环节。而 C 程序在运行前已经把所有逻辑‘翻译妥当’,CPU 拿到的就是最接近硬件的指令流,所以单次循环快得多——但改一行就得重新编译、链接、部署,脚本语言改完保存就能试,适合快速迭代。

脚本语言也讲‘环境’

同样的 Python 脚本,在 Windows 上能跑,在 Linux 上也能跑,靠的不是代码本身多通用,而是 Python 解释器在不同系统上做了适配层。它把 open()os.listdir() 这些调用,悄悄转成对应操作系统的 API。你写的是一套逻辑,背后是解释器在替你‘说不同方言’。