备份语言规范、顶级类型与运行时设计草稿
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
# 框架如何接管代码执行
|
||||
|
||||
框架通过保存定义、关联入口、管理活动状态和调用用户函数来组织执行。语言、执行引擎、宿主和框架的职责分别说明,才能判断一段代码何时运行、在哪运行,以及哪个实体一直存续。基础结构见[12 门语言的执行模型与生命周期](language-runtime-lifecycles.md),词法用途见[关键词手册](language-keywords.md)。
|
||||
|
||||
资料核对日期:2026-10-13。本文以普通 hosted C 进程、CPython/FastAPI/ASGI、Java/Spring Boot/Spring MVC、Unity 6.0,以及 TypeScript/React/Node/Chromium 的指定路径为案例。持续更新的官方文档与实现源码用于说明相应机制,不代表所有历史版本。代码展示预期行为,未作为执行日志;框架的开发模式、配置和替代后端会改变部分时序。
|
||||
|
||||
## 先确定每一层回答什么
|
||||
|
||||
| 层 | 职责 | 典型证据 |
|
||||
| --- | --- | --- |
|
||||
| 语言 | 调用、作用域、暂停、异常、类型等的语义 | Python、Java、C#、ECMAScript 等规范 |
|
||||
| 编译或构建工具 | 检查和转换源码,准备可执行逻辑 | TypeScript 类型擦除、JSX 转换、C# 编译 |
|
||||
| 执行引擎或原生执行环境 | 推进当前代码并维护必要执行状态 | CPython、JVM、V8、Mono、原生机器码 |
|
||||
| 宿主 | 应用入口、I/O、系统事件、线程或事件循环、呈现能力 | ASGI 服务器、Servlet 容器、Node、浏览器、Unity |
|
||||
| 库或框架 | 入口映射、应用状态、组件或请求的组织 | FastAPI 路由、Spring bean、React 组件树 |
|
||||
|
||||
这些是职责边界,不要求每层都是一个独立进程,也不保证每个产品只处在一层。Unity 同时提供引擎宿主与组件管理;Node 包含 V8、原生绑定及 libuv 等协作部分;浏览器还有渲染、网络及多进程组织,不能缩减成 V8 本身。
|
||||
|
||||
注册建立“条件与逻辑”的关系。外部事件或明确调用进入入口;调度机制安排执行机会;引擎执行本次函数。框架也会执行自己的代码,它调用用户函数时通常仍使用语言的普通调用机制。
|
||||
|
||||
## 路径一:C 普通进程
|
||||
|
||||
### 从启动到返回
|
||||
|
||||
这是一个完整的 hosted C 示例,不依赖应用框架。预期输出 `42` 并正常返回成功状态。
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
static int price(int base) {
|
||||
return base + 2;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int result = price(40);
|
||||
printf("%d\n", result);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
启动程序
|
||||
→ 操作系统及实现的启动代码准备执行环境
|
||||
→ 进入 main
|
||||
→ 用户调用 price,当前线程进入该函数
|
||||
→ price 返回 42,继续 main
|
||||
→ printf 产生输出
|
||||
→ main 正常返回,执行正常退出流程
|
||||
```
|
||||
|
||||
`price` 的函数定义不因“程序已经启动”而自己反复执行。这里没有应用任务队列:下一步由当前控制流确定;CPU 最终执行编译后的指令,OS 仍可调度执行线程。[C23 草案的 hosted 启动与终止](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf)
|
||||
|
||||
### 谁持有状态
|
||||
|
||||
| 实体 | 保存的信息 | 结束方式 |
|
||||
| --- | --- | --- |
|
||||
| 程序及静态存储 | 可执行代码与长期数据 | 正常退出或进程终止 |
|
||||
| `main` 调用 | `result`、当前进度及调用关系 | 返回 |
|
||||
| `price` 调用 | `base` 及临时计算 | 返回 42;典型原生调用状态结束 |
|
||||
| 输出设施 | 缓冲和 OS 资源 | 库及退出协议处理 |
|
||||
|
||||
实际 ABI 可以用栈、寄存器和内联等实现调用。语言规定调用结果,具体执行布局由实现决定。添加 GUI 库或 HTTP 服务器后,入口还会增加库管理的事件关系;C 语言本身没有因此改变。
|
||||
|
||||
## 路径二:FastAPI
|
||||
|
||||
### 注册路由与处理请求是两个动作
|
||||
|
||||
以下内容是可交给 ASGI 服务器加载的 Python 模块。`sleep` 用于展示可暂停等待,不代表真实数据库实现。预期请求 `GET /price?base=40` 得到 JSON 数据 `{"price":42}`。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/price")
|
||||
async def price(base: int = 40):
|
||||
await asyncio.sleep(0.01)
|
||||
return {"price": base + 2}
|
||||
```
|
||||
|
||||
加载模块时执行导入、创建 `app`、创建函数及应用装饰器;装饰器把路径、方法和函数等关联起来。它不在注册时替未来每个请求执行 `price` 函数体。请求进入后,框架才使用这份关联选择处理函数。
|
||||
|
||||
服务器以 Uvicorn 为例;事件循环可使用 asyncio 或其兼容实现,而非只能使用 CPython 中某个唯一固定循环。[Uvicorn 事件循环选择](https://uvicorn.dev/concepts/event-loop/)
|
||||
|
||||
### 一次请求经过哪些层
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as 客户端
|
||||
participant Server as ASGI 服务器与事件循环
|
||||
participant App as FastAPI 与 Starlette
|
||||
participant Handler as 用户 price 协程
|
||||
Client->>Server: GET /price?base=40
|
||||
Server->>App: 调用应用:scope、receive、send
|
||||
App->>App: 路由、参数与依赖处理
|
||||
App->>Handler: 调用并推进协程
|
||||
Handler->>Server: 可暂停等待
|
||||
Note over Handler: 局部状态和继续关系被保留
|
||||
Server->>Handler: 条件满足后安排继续
|
||||
Handler-->>App: 返回字典
|
||||
App->>Server: 生成并发送响应事件
|
||||
Server-->>Client: HTTP JSON 响应
|
||||
```
|
||||
|
||||
图示压缩了中间件和序列化细节。ASGI 的 HTTP `scope` 对应一次请求,WebSocket `scope` 则可贯穿整个连接;HTTP keep-alive 不把多个请求合并为同一次处理函数调用。服务器提供协议事件,应用通过可等待的 `receive`/`send` 接收与发送。[ASGI 规范](https://asgi.readthedocs.io/en/latest/specs/main.html)
|
||||
|
||||
| 结构 | 保存的信息 | 管理者与存续 |
|
||||
| --- | --- | --- |
|
||||
| 模块及 `app`、路由定义 | 应用配置和入口映射 | 模块/框架,跨多个请求 |
|
||||
| 请求 scope、Request、依赖求值状态 | 方法、路径、头、输入及本次依赖 | 服务器/Starlette/FastAPI,围绕请求 |
|
||||
| coroutine 继续状态 | `base` 等局部状态、等待后的进度 | Python 执行机制,围绕本次处理 |
|
||||
| Task 及事件循环的等待管理 | 活动状态、等待结果和恢复安排 | ASGI 服务器选定的异步环境 |
|
||||
| 响应对象与输出事件 | 状态码、头及 body | 框架/服务器,直至相应发送流程结束 |
|
||||
|
||||
### 同步函数和异步函数的执行位置
|
||||
|
||||
FastAPI 通过框架调用的普通 `def` 路由函数和同步依赖,通常交给外部线程池;`async def` 路由在异步调用链中推进。这个区别只适用于它负责调用的设施:用户自己在 async 路由中直接调用普通辅助函数,仍是普通同步调用,不自动被挪到线程池。异步函数中的 CPU 循环或阻塞 I/O 仍能阻碍事件循环。[FastAPI 的技术说明](https://fastapi.tiangolo.com/async/#very-technical-details)
|
||||
|
||||
### 应用、请求和依赖分别清理
|
||||
|
||||
应用的 `lifespan` 上下文管理共享资源:进入部分用于应用接收请求前的准备,退出部分用于关闭。带 `yield` 的依赖围绕更小的工作范围获取和释放资源。当前文档区分 `scope="function"` 的函数退出清理与 `scope="request"` 的响应发送后清理;不要把两者写成一个固定时间点。[应用 lifespan](https://fastapi.tiangolo.com/advanced/events/)、[带 yield 依赖的 scope](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#early-exit-and-scope)
|
||||
|
||||
处理函数返回不意味着应用退出;请求结束不意味着共享连接池必须销毁。失败进入异常处理路径,取消及断连的传播要结合服务器、中间件和被等待操作判断,不能承诺客户端断开后所有业务效果立即停止或回滚。
|
||||
|
||||
## 路径三:Spring Boot
|
||||
|
||||
### 从入口启动容器
|
||||
|
||||
基线是使用 Spring MVC 的应用及嵌入式 Servlet 容器,Tomcat 可作为例子。Spring Boot 组织启动与配置;Spring 的 `ApplicationContext` 管理 bean;Servlet 容器接入 HTTP;JVM 执行 Java 方法。这些不是同一个生命周期。
|
||||
|
||||
下面是 `example.pricing` 包中的 `PriceApplication.java` 示例,前提是项目具有相应 Spring Boot Web 依赖。预期请求 `GET /price?base=40` 返回包含 `price: 42` 的 JSON 对象。
|
||||
|
||||
```java
|
||||
package example.pricing;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
public class PriceApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PriceApplication.class, args);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PriceController {
|
||||
@GetMapping("/price")
|
||||
public Map<String, Integer> price(
|
||||
@RequestParam(name = "base", defaultValue = "40") int base) {
|
||||
return Map.of("price", base + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
启动时由容器读取配置、发现并创建所需 bean、注入依赖及调用相应初始化设施。映射注解是供框架使用的元数据;Java 的 `@GetMapping` 拼写不是新增 Java 关键词,声明它也不等于马上处理请求。[Spring Boot 的启动](https://docs.spring.io/spring-boot/reference/features/spring-application.html)、[bean 与依赖注入](https://docs.spring.io/spring-boot/reference/using/spring-beans-and-dependency-injection.html)
|
||||
|
||||
### 请求怎样进入 controller
|
||||
|
||||
```text
|
||||
HTTP 请求
|
||||
→ Servlet 容器接入并安排处理
|
||||
→ Filter 等链路
|
||||
→ DispatcherServlet
|
||||
→ HandlerMapping 查找匹配的处理器
|
||||
→ HandlerAdapter 配合参数解析等设施调用 controller 方法
|
||||
→ JVM 推进本次 Java 调用
|
||||
→ 返回值处理及消息转换形成 JSON 响应
|
||||
→ 容器写回客户端
|
||||
```
|
||||
|
||||
这里选择 `@RestController` 的数据响应路径;返回视图名的 controller 还会涉及视图解析。URL 没有一个“执行 Java”的特殊开关,服务端配置的分派逻辑决定产生响应的方法。[DispatcherServlet](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html)、[请求处理序列](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/sequence.html)
|
||||
|
||||
| 结构 | 保存的信息 | 管理者与存续 |
|
||||
| --- | --- | --- |
|
||||
| JVM 的类、方法元数据 | 编译后的 Java 逻辑 | JVM/类加载器 |
|
||||
| bean definition 与 ApplicationContext | 创建配方、依赖关系及受管理实例 | Spring,跨请求或按配置的 scope |
|
||||
| 请求映射与 handler 元数据 | 路径、方法、参数及调用入口 | Spring MVC,跨请求 |
|
||||
| Servlet 请求/响应及分派状态 | 本次 HTTP 数据和处理关联 | 容器及框架,围绕一次请求 |
|
||||
| JVM 方法 frame | 参数、局部变量及方法进度 | 本次 Java 调用 |
|
||||
|
||||
默认 singleton bean 在容器内被复用,不是每个请求都创建一个 controller。方法 frame 是本次调用的状态;把请求数据存入复用实例字段会涉及并发访问。这是实例存续与请求存续不同带来的后果。prototype 等 scope 的退出管理也有不同保证,不能认为所有 bean 都由容器完整管理至销毁。[Spring bean scope](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html)
|
||||
|
||||
### 初始化、请求返回与关闭
|
||||
|
||||
Spring 可以调用 `@PostConstruct`、初始化接口或配置的 init 方法;正常关闭时可调用 `@PreDestroy` 等销毁设施。`Lifecycle`/`SmartLifecycle` 还表达受管理活动的启停。回调由容器在相应条件下调用,不由 Java GC 替代;具体 bean scope 的管理保证需要分别核对。[Spring bean 生命周期](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html)
|
||||
|
||||
请求方法返回后应用继续存续。Boot 的正常退出支持关闭 ApplicationContext 的安排;强制终止不能被当作所有清理钩子都会执行的正常关闭。[Spring Boot 应用退出](https://docs.spring.io/spring-boot/reference/features/spring-application.html#features.spring-application.application-exit)
|
||||
|
||||
### WebFlux 是另一种请求模型
|
||||
|
||||
Spring Boot 并不只代表 Servlet 阻塞模型。WebFlux 使用非阻塞契约、响应式组合及服务器适配;通常用较少的事件循环工作线程推进活动,和 MVC 常见的阻塞处理假设不同。`Mono`/`Flux` 保存或组合数据流协议,不等同于每个请求专属的一条 JVM 调用栈。这里的 Reactor `Mono` 是响应式库类型,与 Unity 的 Mono 执行后端无关。MVC 也能使用异步设施,不能把二者概括成“Java 一定阻塞”和“Java 一定事件循环”。[Spring WebFlux 的并发模型](https://docs.spring.io/spring-framework/reference/web/webflux/new-framework.html)
|
||||
|
||||
## 路径四:Unity
|
||||
|
||||
### 执行后端与阶段安排是两个问题
|
||||
|
||||
Unity 的 C# 脚本通常先编译为 IL 等内容。Mono 后端提供托管执行与 JIT;IL2CPP 将 IL 转为 C++ 再编译为原生代码。不能直接把 Unity 写成桌面 CoreCLR 应用。无论采用哪条后端路径,引擎依然承担场景与组件管理,以及 PlayerLoop 的阶段调用安排。[Unity 脚本后端](https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends.html)
|
||||
|
||||
```text
|
||||
开发准备:C# 源码 → IL → Mono 执行路径/IL2CPP 原生编译路径
|
||||
|
||||
应用运行:Unity 启动与场景准备
|
||||
→ 建立 GameObject 与组件实例及关系
|
||||
→ 根据对象活动、组件启用与阶段规则调用事件函数
|
||||
→ PlayerLoop 推进输入、物理、脚本更新及其他系统
|
||||
→ 场景卸载、禁用或销毁触发相应处理
|
||||
```
|
||||
|
||||
PlayerLoop 是引擎的一组系统阶段,还能通过相关 API 定制;并不是每一帧把项目里所有 C# 函数都运行一次。[Unity 事件执行顺序](https://docs.unity3d.com/6000.0/Documentation/Manual/execution-order.html)
|
||||
|
||||
### 哪些回调在什么条件下进入
|
||||
|
||||
| 入口 | 管理条件 | 不能推出的结论 |
|
||||
| --- | --- | --- |
|
||||
| `Awake` | 组件实例加载、对象激活等符合条件的初始化,一实例调用一次 | 不是所有不活动对象都在场景加载时立即执行,也不是 C# 普通构造器 |
|
||||
| `OnEnable` | 对象活动且组件启用时 | 不是一生只调用一次,可因再次启用进入 |
|
||||
| `Start` | 启用后首次更新前的一次初始化 | 不保证与 Awake 同帧;禁用时可能延后 |
|
||||
| `FixedUpdate` | 固定时间步的物理更新安排 | 每个显示帧可能零次、一次或多次 |
|
||||
| `Update` | 符合条件的脚本帧更新 | 不是全部数据对象的通用钩子 |
|
||||
| `LateUpdate` | 在 Update 后的相应阶段 | 不代替任意线程的同步协议 |
|
||||
| `OnDisable` | 组件或对象退出启用活动等相应情形 | 禁用和销毁不是同一状态 |
|
||||
| `OnDestroy` | 满足条件的组件或对象销毁 | 有适用边界;强制退出不能保证执行 |
|
||||
|
||||
条件详见 [Awake](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Awake.html)、[OnEnable](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnEnable.html)、[Start](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Start.html)、[FixedUpdate](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.FixedUpdate.html)、[Update](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.Update.html)、[LateUpdate](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.LateUpdate.html)、[OnDisable](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnDisable.html)、[OnDestroy](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnDestroy.html)。
|
||||
|
||||
以下是应保存为 `PriceLabel.cs` 并挂在活动 GameObject 上的组件例子。这里用日志观察执行,不代表 UI 的正式绘制实现。
|
||||
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
|
||||
public class PriceLabel : MonoBehaviour
|
||||
{
|
||||
private int price;
|
||||
|
||||
private void Awake() { price = 42; }
|
||||
private void OnEnable() { Debug.Log("enabled"); }
|
||||
private void Start() { Debug.Log(price); }
|
||||
private void Update() { /* 每次符合条件的更新处理 */ }
|
||||
private void OnDisable() { Debug.Log("disabled"); }
|
||||
}
|
||||
```
|
||||
|
||||
在常规初始化条件下,实例准备→Awake→OnEnable→Start→后续 Update。重新禁用再启用,可以再发生 OnEnable;这不意味着 price 字段被重新创建或 Start 再次执行。同类不同对象的调用顺序不能仅凭源码文件排列推断。
|
||||
|
||||
### 哪些状态跨帧存在
|
||||
|
||||
| 结构 | 保存的信息 | 管理者与存续 |
|
||||
| --- | --- | --- |
|
||||
| 场景、GameObject 与组件关系 | 对象组织、活动及启用状态 | Unity 引擎,跨帧 |
|
||||
| 脚本实例及字段 | `price` 等长期组件状态 | 托管对象及引擎对象关联,跨回调 |
|
||||
| 每次 Update 等的方法调用状态 | 本次局部变量和当前进度 | 执行后端,一次回调 |
|
||||
| IEnumerator 状态机与协程等待记录 | 跨 yield 的局部数据、继续位置及等待条件 | 编译器表示及 Unity 协程调度 |
|
||||
| 引擎资源与托管引用 | 场景对象、资源及引用关系 | 引擎销毁和托管回收分别处理 |
|
||||
|
||||
普通 `Update` 返回后,下帧是另一次函数调用;Unity 协程 `yield return` 后则由引擎按等待条件继续迭代状态。`StartCoroutine` 不自动创建后台线程;同步耗时计算仍可占用主线程。停止条件也取决于对象、组件及协程 API,不能把“组件 disabled”当成所有任务统一取消协议。[Unity 协程](https://docs.unity3d.com/6000.0/Documentation/Manual/Coroutines.html)
|
||||
|
||||
## 路径五:React
|
||||
|
||||
### 构建不是应用运行的中间调度层
|
||||
|
||||
```text
|
||||
开发及构建:
|
||||
TS/TSX 源码
|
||||
→ 类型检查、类型处理、JSX 转换及模块处理
|
||||
→ 供目标宿主执行的 JavaScript
|
||||
|
||||
运行:
|
||||
浏览器:启动 JS → React 组织组件计算 → DOM 提交 → 浏览器呈现
|
||||
服务器:Node 执行 JS → React 服务端渲染 → 输出 HTML 等响应
|
||||
```
|
||||
|
||||
转换可以分散在 TypeScript、其他转换器和打包工具中;JSX 也可以先被保留。构建工具运行在 Node,不证明组件随后一定在 Node 执行。React 是正在被执行的库代码,会组织用户组件及更新的调用;TS 类型通常已擦除。[TypeScript 的类型擦除](https://www.typescriptlang.org/docs/handbook/2/basic-types.html#erased-types)、[JSX 转换模式](https://www.typescriptlang.org/docs/handbook/jsx.html)
|
||||
|
||||
### 浏览器首次显示与一次点击
|
||||
|
||||
下面是客户端入口 TSX 的例子,前提是页面中有 `id="root"` 的容器,且构建环境提供 React、React DOM 和相应类型声明。它是普通客户端渲染,不含 SSR。
|
||||
|
||||
```tsx
|
||||
import { useEffect, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function Price() {
|
||||
const [price, setPrice] = useState(42);
|
||||
|
||||
useEffect(() => {
|
||||
const previousTitle = document.title;
|
||||
document.title = `Price: ${price}`;
|
||||
return () => { document.title = previousTitle; };
|
||||
}, [price]);
|
||||
|
||||
return <button onClick={() => setPrice(p => p + 1)}>{price}</button>;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<Price />);
|
||||
```
|
||||
|
||||
预期首次按钮显示 42,点击后显示 43;提交后的 Effect 同步相应标题。示例的非空断言不构成运行时容器创建,HTML 必须实际提供该节点。调用 root.render 提交渲染请求,也不能据此认为调用返回时所有 DOM 和 Effect 工作已经完成。[createRoot 与 render](https://react.dev/reference/react-dom/client/createRoot)
|
||||
|
||||
| 步骤 | 谁进入或调用 | 保存或使用的状态 |
|
||||
| --- | --- | --- |
|
||||
| 加载入口 JS | 浏览器的脚本加载与求值机制 | JS 模块、函数及 React 库 |
|
||||
| 首次 render 请求 | 入口调用 React root API | root、更新及组件关系 |
|
||||
| 组件计算 | React 调用 `Price` | 本次调用的局部值,React 持有的 Hook 状态 |
|
||||
| commit | React 调用 DOM 等宿主接口 | 已提交的树及 DOM 关系 |
|
||||
| Effect setup | React 按 Effect 规则安排 | 依赖和返回的 cleanup |
|
||||
| 用户点击 | 浏览器事件分派进入 React 事件处理,再调用用户处理逻辑 | 事件、组件关联及更新请求 |
|
||||
| 后续更新 | React 处理状态更新,再组织 render/commit | 新状态,旧依赖及新依赖 |
|
||||
| 卸载 | root 或组件树操作使组件退出 | Effect cleanup、宿主节点及关联清理 |
|
||||
|
||||
render 是计算下一份界面描述,commit 才将相应变化应用到宿主。组件函数可以被多次调用;应保持渲染计算纯净,不能把“函数被调用一次”当作“一次业务操作已经提交”的保证。[React render 与 commit](https://react.dev/learn/render-and-commit)、[组件与 Hook 的纯度要求](https://react.dev/reference/rules/components-and-hooks-must-be-pure)
|
||||
|
||||
### 状态为什么没有随组件函数返回而消失
|
||||
|
||||
React 持有组件关系及 Hook 状态,组件函数每次调用读取当前状态。普通局部变量随调用建立,`useState` 管理的状态则与 React 管理的组件身份关联,不能当作每次新建的普通局部值。[React 状态的保留与重置](https://react.dev/learn/preserving-and-resetting-state)
|
||||
|
||||
| 结构 | 职责 | 所属层 |
|
||||
| --- | --- | --- |
|
||||
| JS 函数、闭包和本次调用状态 | 执行组件计算及事件函数 | JavaScript 引擎 |
|
||||
| React element 等界面描述 | 表达本次计算所得结构 | React 库数据 |
|
||||
| Fiber 与 root | 组件工作、父子关联、已保存状态、更新及待处理工作 | React 当前协调实现 |
|
||||
| Hook 状态与 Effect 记录 | 状态、依赖及 cleanup 关联 | React 管理结构 |
|
||||
| DOM 节点与事件设施 | 浏览器中的宿主呈现及交互 | 浏览器 |
|
||||
|
||||
Fiber 是实现结构,源码包含子节点、相邻节点、更新队列和保存状态等信息。它不等于 V8 调用栈,也不是所有 React API 使用者必须直接创建的对象;字段和布局不是稳定应用 API。[React 实现中的 Fiber 类型](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js)
|
||||
|
||||
### React Hook 与 Unity 阶段回调的差别
|
||||
|
||||
`useState`、`useEffect` 等普通 Hook 必须服从顶层调用与顺序约束,它们不是可以任意在条件中增删的动态回调列表。`useEffect` 记录的 setup/cleanup 与依赖变化关联;`useState` 则管理状态,并不代表事件监听。特殊的 `use` API 有不同的调用规则,不能用“所有 use 开头函数都绝对相同”概括。[Hook 规则](https://react.dev/reference/rules/rules-of-hooks)、[use API](https://react.dev/reference/react/use)
|
||||
|
||||
Effect 在组件提交后按相关规则同步外部系统;依赖变化时先清理旧关联,再建立新关联,卸载时清理。开发 Strict Mode 会增加用于检查的 setup/cleanup 周期。Effect 与浏览器实际绘制的先后不能简单固定为“每次 paint 之后”;`useEffect` 与 `useLayoutEffect` 的契约不同。[useEffect 的时序与边界](https://react.dev/reference/react/useEffect)
|
||||
|
||||
Unity 主要用引擎命名阶段选择符合条件的组件回调;React 主要用更新、组件计算、提交和依赖关联组织 UI。React 不要求组件每个显示帧都重新计算,也没有为每个普通函数自动提供 Awake/Update。
|
||||
|
||||
### 可选的服务端渲染与 hydration
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser as 浏览器进程
|
||||
participant Server as Node 服务器进程
|
||||
participant SSR as 服务器 React 渲染
|
||||
participant ClientReact as 浏览器 React
|
||||
Browser->>Server: 页面请求
|
||||
Server->>SSR: 调用服务端渲染入口
|
||||
SSR-->>Server: HTML 流等结果
|
||||
Server-->>Browser: HTTP 响应与后续资源
|
||||
Browser->>Browser: 解析 HTML 并加载客户端 JS
|
||||
Browser->>ClientReact: hydrateRoot 等入口
|
||||
ClientReact->>Browser: 关联交互逻辑与已有 DOM
|
||||
```
|
||||
|
||||
`renderToPipeableStream` 是 Node 流环境的一条 React 服务端输出路径;浏览器的 `hydrateRoot` 接管已有的服务端 HTML 并建立客户端交互。初始渲染结果要符合 hydration 的一致性要求,不能把服务器函数对象、闭包或堆地址当成直接转移到浏览器的状态。[服务端流渲染](https://react.dev/reference/react-dom/server/renderToPipeableStream)、[hydrateRoot](https://react.dev/reference/react-dom/client/hydrateRoot)
|
||||
|
||||
服务端和客户端分别拥有自己的 JS 执行环境与对象。Effects 不在服务端渲染时运行;需要客户端的交互逻辑仍由浏览器加载和执行。SSR、React Server Components、服务器函数与框架路由是不同机制;本节追踪传统 SSR/hydration,不把其他机制默认叠加进去。
|
||||
|
||||
## Node 与 Chromium
|
||||
|
||||
### 请求得到什么与客户端怎么使用分别决定
|
||||
|
||||
服务端先按路由或静态资源配置生成响应,客户端再按请求用途、响应信息和自己的规则使用内容。URL 的后缀不能单独决定代码是在服务器执行还是在浏览器执行。
|
||||
|
||||
| 动作 | 服务端可能怎样生成响应 | 浏览器接收后的典型路径 |
|
||||
| --- | --- | --- |
|
||||
| 导航到页面 URL | 静态 HTML 或程序生成 HTML | 导航及文档解析、样式、布局和呈现 |
|
||||
| 页面引用 `<script src="/app.js">` | 读取文件或程序生成 JS | 脚本机制在满足加载、类型及策略条件后安排执行 |
|
||||
| `fetch("/app.js")` | 也可能是同一份 JS 字节 | 得到 Response,读取为文本不自动执行该文本 |
|
||||
| `fetch("/price")` 并解析 JSON | 服务端处理函数计算再序列化 | 创建本地 JS 数据;用户代码决定怎样更新界面 |
|
||||
|
||||
脚本加载和执行规则由 HTML 宿主机制规定;Fetch 返回响应的 API 本身不包含“自动把响应当脚本执行”。直接打开 JS 或 JSON 资源时,显示或下载还受导航、媒体类型和响应头等规则影响,不能假定所有浏览器都采用相同展示界面。[HTML script 规则](https://html.spec.whatwg.org/multipage/scripting.html#the-script-element)、[Fetch 方法](https://fetch.spec.whatwg.org/#fetch-method)
|
||||
|
||||
服务器即使返回 JSON,浏览器也可以显示文本;具有特定应用含义的按钮、图表等需要客户端程序、HTML 或相应查看器的呈现规则。JSON 的数据结构本身不会自动定义完整应用 UI。
|
||||
|
||||
### 浏览器与 Node 的任务管理
|
||||
|
||||
| 维度 | Chromium 中的页面 | Node 应用 |
|
||||
| --- | --- | --- |
|
||||
| 入口 | 导航、脚本/模块、事件、控制台求值等 | 入口模块、REPL、服务器及其他 API 回调 |
|
||||
| 系统事件 | 用户交互、网络、定时器、页面生命周期等 | 网络、文件、定时器、进程等 |
|
||||
| 调度组织 | HTML 任务来源、任务队列、微任务检查点及渲染机会 | libuv 阶段、Node 回调边界及微任务处理 |
|
||||
| 呈现 | DOM、样式、布局、绘制等浏览器设施 | 默认没有页面 DOM 和浏览器绘制阶段 |
|
||||
| 长期状态 | 文档、DOM、监听关联、JS 环境、框架状态 | 模块、服务器、连接、监听关联、JS 环境 |
|
||||
| 实际执行 JS | V8 | V8 |
|
||||
|
||||
浏览器规范中的正式概念是 task;“宏任务”是常见教学叫法。不能把浏览器描述成唯一全局 FIFO 队列。微任务检查点会处理新加入的微任务,渲染还要符合渲染机会条件。[HTML 事件循环](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)
|
||||
|
||||
Node 的 timers、poll、check 等阶段来自宿主机制;`setImmediate` 和 `process.nextTick` 不是 ECMAScript 关键词或 V8 自带的全部循环。`nextTick` 的先后还要考虑调用上下文及模块求值方式,不能承诺它在所有情境绝对先于一切 Promise 工作。[Node 阶段与 nextTick](https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick)
|
||||
|
||||
V8 提供上下文、代码执行和微任务相关能力,宿主通过嵌入接口配合调度。因此“V8 完全没有任何队列”也不准确;完整 I/O 与应用事件循环仍不能统称为 V8。[V8 嵌入说明](https://v8.dev/docs/embed)、[V8 MicrotaskQueue API](https://v8.github.io/api/head/classv8_1_1MicrotaskQueue.html)
|
||||
|
||||
### 同步调用、事件和 REPL 三种入口
|
||||
|
||||
Node HTTP 服务器把请求函数登记到服务器的 request 事件关系中;收到请求后,宿主和服务器设施进入回调,V8 执行用户 JS。运行到普通 `helper()` 调用时则立即进入该函数,不等待另一个 HTTP 事件。监听器是长期关联,每次调用状态是短期上下文。[Node HTTP request 事件](https://nodejs.org/api/http.html#event-request)
|
||||
|
||||
Node REPL 管理输入流、求值上下文、结果输出及会话关闭等;浏览器控制台在所选页面或 frame 的环境中求值,能接触对应 DOM 等宿主对象。它们可以使用同一引擎的求值能力,但会话、绑定规则、可用 API 和后台活动不同。REPL 等输入时,进程不需要一直忙跑一段用户 JS 循环。[Node REPL](https://nodejs.org/api/repl.html)、[Chrome 控制台执行 JavaScript](https://developer.chrome.com/docs/devtools/console/javascript)
|
||||
|
||||
## 五条路径放在同一张表里
|
||||
|
||||
| 路径 | 触发与入口选择 | 执行的推进者 | 跨执行保留的主要状态 | 普通工作结束 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| C 普通进程 | 启动入口、用户调用 | 原生代码及 OS 线程调度 | 程序数据,调用期间的状态 | 返回或退出 |
|
||||
| FastAPI | 服务器接入、框架路由及依赖 | CPython、选定异步环境或线程池 | app、路由、请求、Task/协程等待状态 | 响应及相应范围清理 |
|
||||
| Spring MVC | Servlet 容器、DispatcherServlet | JVM 与容器执行安排 | bean、映射、请求及方法状态 | 返回响应;容器继续存续 |
|
||||
| Unity | 引擎状态与 PlayerLoop 阶段 | Mono/IL2CPP 路径及引擎 | 场景、组件字段、协程等待记录 | 回调返回、禁用或销毁分别发生 |
|
||||
| React 浏览器 | root 更新、状态变化和事件 | React 组织调用,V8 执行,浏览器呈现 | 组件关系、Hook、更新、Effect、DOM | 计算完成、提交和卸载分别发生 |
|
||||
|
||||
钩子表达触发关系,调度器安排工作,解释器或其他执行机制推进代码;数据结构保存跨这些动作的关系。状态与迁移规则、归属与释放规则则决定哪些动作合法,以及活动何时结束。它们不能只用“有一个循环”替代。
|
||||
|
||||
## 对 Endorphin 执行模型的设计分析
|
||||
|
||||
本节提出研究维度及验证场景,不确认新的语法、原生任务类型或调度 API。现有语言决定仍以 [Preflight 与 inflight](../guide/05-preflight-and-inflight.md)、[函数与调用](../guide/09-functions-and-calls.md)、[Object 与类型系统](../guide/07-top-level-types/README.md)、[资源访问与呈现](../guide/11-resource-access-and-presentation.md)、[REPL 提案](../guide/14-repl.md)和[规范状态](specification-status.md)为准。
|
||||
|
||||
### 先描述可观察的执行契约
|
||||
|
||||
| 维度 | 需要形成的明确契约 | 可用的验证场景 |
|
||||
| --- | --- | --- |
|
||||
| 定义与活动 | 定义准备、对象创建、调用开始分别由什么动作发生 | 查看函数或导入是否触发业务调用 |
|
||||
| 执行入口 | 显式调用、REPL 输入、界面事件和平台触发怎样进入 | 提交参数后何时真正执行定价函数 |
|
||||
| 一次执行的单位 | 本次调用及其子调用、等待和结果如何关联 | 同一函数同时接受两个不同输入 |
|
||||
| 上下文 | 代码与依赖、输入、局部状态、进度、结果接收方保存在哪里 | 等待后继续使用原来的输入 |
|
||||
| 顺序与更新边界 | 哪些工作必须有序,何时发布可观察结果 | 同一事件引起两个更新,界面看到什么 |
|
||||
| 等待与继续 | 保留本次状态、等待条件、恢复入口与失败传播 | 数据未就绪后继续执行,而非重复业务写入 |
|
||||
| 并发与隔离 | 同一对象上的活动如何协调,哪些状态可共享 | 两次执行同时更新同一个运行对象 |
|
||||
| 取消与归属 | 谁能结束活动,子活动和资源如何跟随 | REPL 关闭或 UI 消失后仍在等待的计算 |
|
||||
| 位置与权限 | 平台怎样决定执行位置和有效能力 | 同一函数被不同身份使用,跨端对象如何关联 |
|
||||
| 清理与保存 | 活动结束、资源解除、结果留存和对象回收分别何时发生 | 查看历史结果不重放计算,订阅结束能释放关联 |
|
||||
|
||||
执行记录可以引用平台对象,但数据对象、函数定义、调用状态、结果句柄、触发关联的职责仍需区分。拥有平台 ID 并不自动意味着需要执行;存储局部变量也不自动意味着具备跨进程恢复能力。持久化正在执行的状态还涉及代码版本、外部操作及恢复边界,不能从“状态可保存”直接推出可靠重启。
|
||||
|
||||
### 用一条轨迹检查规则是否完整
|
||||
|
||||
下面是研究用的行为轨迹,不是 Endorphin 源码或新增 API。
|
||||
|
||||
```text
|
||||
定义已准备
|
||||
→ 用户明确提交一组输入
|
||||
→ 建立本次执行及有效上下文
|
||||
→ 获得执行机会并进入函数
|
||||
→ 遇到未就绪操作,保留进度与等待关联
|
||||
→ 条件满足后再次获得执行机会
|
||||
→ 从继续位置推进,交付结果或失败
|
||||
→ 解除本次活动资源;按规则保留结果或关联
|
||||
```
|
||||
|
||||
这条轨迹至少能区分“输入还没准备好”“已经执行但正在等待”“已经完成而结果仍被查看”。如果界面重新呈现结果不应重新运行写入,就需要明确结果查看与调用触发的边界;如果不允许自动重试,就不能以重算替代恢复。
|
||||
|
||||
### 再选择队列与钩子的组织方式
|
||||
|
||||
以下是设计比较,不是已采用方案。
|
||||
|
||||
| 候选组织方式 | 适合表达的要求 | 必须另外处理的边界 |
|
||||
| --- | --- | --- |
|
||||
| 直接同步调用 | 明确调用关系及立即返回 | 长时间运行、等待及并发活动 |
|
||||
| 固定阶段回调 | 稳定周期或固定阶段的工作 | 不活动对象、阶段顺序及启停规则 |
|
||||
| 事件订阅 | 按事件选择处理逻辑 | 订阅所有者、取消、重复事件及积压 |
|
||||
| 可暂停任务与继续关系 | 等待后保留本次计算的进度 | 调度、公平性、失败、取消及资源归属 |
|
||||
| 组件更新与提交边界 | 把计算与可观察界面更新关联 | 可重算性、副作用和结果发布时机 |
|
||||
| 分层队列或检查点 | 区分不同工作的顺序及更新边界 | 饥饿、重入、无限连锁更新及跨队列顺序 |
|
||||
|
||||
宏任务/微任务只是一种组织顺序的具体办法。Endorphin 是否需要类似机制,应由事件处理、继续计算和结果呈现之间的顺序要求决定。固定阶段与依赖驱动的更新也可以服务不同实体,不需要先把所有对象放入一个通用循环。
|
||||
|
||||
远程数据同步可用于检查跨端身份、变化到达和更新顺序;它只是验证场景之一。执行模型的主问题仍是:入口怎样建立活动,执行如何推进,等待怎样保存并继续,以及活动由谁结束。
|
||||
|
||||
[12 门语言的执行模型与生命周期](language-runtime-lifecycles.md) · [关键词手册](language-keywords.md) · [返回目录](../README.md)
|
||||
Reference in New Issue
Block a user