在菜鸟教程里,点一下“运行”,Python 立刻有了输出。
可浏览器本身并不认识 Python:这段代码究竟被谁接住,又是怎样回到页面上的?
菜鸟教程代码运行示例代码走过四站
流程图负责说明先后顺序;每一站的插画,负责说明这一站到底在做什么。
浏览器代码运行的四个阶段01 代码块交出输入
代码围栏不负责运行,只交出语言和源码。dataset 读取语言,textContent 读取代码,合成输入对象。
阶段一:读取代码和语言type CodeInput = { language: string; code: string };const card = document.querySelector<HTMLElement>("[data-example]")!;const input: CodeInput = { language: card.dataset.language ?? "", code: card.querySelector("[data-code]")?.textContent ?? "",};
得到:{ language, code }。
02 适配器组装 Runner
页面只调用统一的 run。createRunner 根据语言选适配器,适配器再连接运行时。
阶段二:适配器选择 Runnertype CodeRunner = { run(code: string): Promise<RunResult>; dispose(): void;};function createRunner(language: string): CodeRunner { if (language === "python") { return createWorkerRunner("./workers/python.worker.js"); } throw new Error("暂未注册 Runner");}
Python 是当前注册的第一种语言。
03 Worker 里真正执行
Runner 创建 Worker,把代码发过去。Worker 加载 Pyodide,用 setStdout 接住输出,再用 runPythonAsync 执行。
阶段三:Worker 使用 Pyodide 执行// 主线程const worker = new Worker(workerUrl);worker.postMessage({ type: "run", code });// workers/python.worker.jsself.onmessage = async ({ data }) => { const pyodide = await loadPyodide({ indexURL: PYODIDE_INDEX_URL }); let stdout = ""; pyodide.setStdout({ batched:text => (stdout += text) }); await pyodide.runPythonAsync(data.code); self.postMessage({ type: "result", ok: true, stdout });};
workers/python.worker.js 是项目自己写的承载文件。
04 消息带着结果回来
Worker 完成后用 postMessage 回传结果,主线程在 onmessage 中把 stdout 或 stderr 写进输出面板。
阶段四:消息带回执行结果worker.onmessage = ({ data }) => { if (data.type !== "result") return; output.textContent = data.ok ? data.stdout : data.stderr;};
页面看到的是统一结果,不需要知道 Python 内部怎么跑。
回头看刚才的点击:代码块交出字符串,Runner 找到运行时,Worker 完成计算,消息把结果带回页面。
换成其它语言呢?
只替换语言适配器:JavaScript 可以用原生 Worker,SQL 可以接 SQLite-Wasm 或 DuckDB-Wasm。代码块、消息格式和输出面板都能继续复用。
它还能做什么?
教程里的可运行示例、浏览器内的数据处理和 SQL 小工具、无需上传代码的交互式练习,都可以沿用这条“输入 → 执行 → 输出”的路径。
