微信小程序和小游戏底层逻辑:从运行时链路开始理解

📚 前言

如果要系统学习微信小程序和小游戏,不建议一开始就背 API。

更稳的入口是先问一个问题:

小程序和小游戏的代码,到底跑在哪里?

很多现象都能从这个问题推出来:

  • 为什么小程序没有完整 DOM?
  • 为什么更新页面要用 setData?
  • 为什么频繁 setData 会卡?
  • 为什么小程序能调用扫码、支付、定位这些原生能力?
  • 为什么小游戏可以直接画 Canvas,却不能像网页一样操作 DOM?

这篇文章作为专栏第一篇,只做一件事:

先把微信小程序 / 小游戏的运行链路串起来。


🧭 先用一句话理解

微信小程序和小游戏本质上都不是普通网页。

它们更像是:

1
运行在微信客户端里的受控前端应用

也就是说:

1
2
3
4
5
6
7
你的代码
↓
微信基础库
↓
微信客户端 Native 宿主环境
↓
iOS / Android 系统能力

微信客户端才是真正的宿主。

小程序和小游戏只是运行在这个宿主环境里的不同形态。


一、从普通网页说起

普通网页的运行环境大概是:

1
2
3
4
5
6
7
HTML
CSS
JavaScript
↓
浏览器内核
↓
DOM / BOM / Canvas / 网络 / 存储

在浏览器里,JS 可以直接操作 DOM:

1
document.querySelector('#app').innerHTML = 'hello'

浏览器负责:

  • 解析 HTML
  • 解析 CSS
  • 执行 JS
  • 构建 DOM 树
  • 计算样式
  • 布局
  • 绘制
  • 合成

所以浏览器里的前端应用,本质是:

1
JS 直接驱动浏览器页面

但是小程序不是这样。


二、小程序为什么不是普通网页

小程序没有把完整浏览器能力直接暴露给开发者。

你写的是:

1
2
3
4
WXML
WXSS
JS
JSON

而不是:

1
2
3
HTML
CSS
JavaScript

这不是简单换名字。

它背后代表的是:

1
微信重新设计了一套受控的前端运行模型

开发者不能随便操作 DOM,也不能随便访问所有浏览器 API。

你写:

1
2
3
this.setData({
title: 'hello'
})

而不是:

1
document.querySelector('.title').textContent = 'hello'

这是理解小程序底层逻辑的第一个关键点:

小程序不是让 JS 直接操作页面,而是让 JS 把数据交给微信框架,再由微信框架更新页面。


三、小程序的核心架构:逻辑层和渲染层

小程序最经典的模型是双线程架构。

可以先这样理解:

1
2
3
4
5
逻辑层 AppService
负责 JS 逻辑、数据、生命周期、事件处理

渲染层 View
负责 WXML / WXSS 渲染页面

它们不是同一个执行环境。

大致链路是:

1
2
3
4
5
6
7
8
9
10
11
12
13
用户点击页面
↓
渲染层捕获事件
↓
微信客户端 Native 中转
↓
逻辑层执行事件回调
↓
逻辑层调用 setData
↓
Native 中转数据
↓
渲染层更新视图

也就是:

1
View → Native → AppService → Native → View

这条链路非常重要。

后面所有性能问题,基本都能从这条链路里找到原因。


四、setData 到底在做什么

在小程序里,setData 不是普通赋值。

它更像是:

1
逻辑层向渲染层发送一份视图更新消息

例如:

1
2
3
this.setData({
count: this.data.count + 1
})

可以理解成:

1
2
3
4
5
6
7
逻辑层数据变化
↓
序列化变更数据
↓
通过 Native Bridge 发送给渲染层
↓
渲染层根据数据更新 WXML 对应视图

所以 setData 有几个天然成本:

  • 数据要跨线程传输
  • 数据通常要序列化
  • 渲染层要接收并应用更新
  • 更新太频繁会阻塞通信队列

因此小程序性能优化里经常会说:

1
2
3
不要频繁 setData
不要传太大的 data
只传真正变化的字段

这不是经验玄学,而是由底层链路决定的。


五、JSBridge 在哪里出现

小程序和小游戏都运行在微信客户端里。

那么问题来了:

JS 怎么调用微信扫码、支付、登录、定位这些原生能力?

答案是:

1
JSBridge

可以把 JSBridge 理解成:

1
JS 和 Native 之间的通信协议层

大概是:

1
2
3
4
5
6
7
8
9
JS 调用 wx API
↓
微信基础库封装参数
↓
通过 JSBridge 发给微信 Native
↓
Native 调用系统或微信能力
↓
Native 把结果回传给 JS

例如:

1
2
3
4
5
wx.login({
success(res) {
console.log(res.code)
}
})

背后可以理解为:

1
2
3
4
5
6
7
JS 发起 login 请求
↓
微信客户端处理登录态
↓
返回临时 code
↓
JS 回调拿到结果

所以 wx.xxx API 不是普通 JS 函数。

它本质是:

1
前端请求微信客户端代为执行某个能力

六、小程序页面更新链路

把前面的内容合起来,小程序页面的一次更新可以这样理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
用户操作
↓
渲染层触发事件
↓
事件通过 Native 传给逻辑层
↓
逻辑层执行 JS
↓
调用 setData
↓
数据通过 Native 传给渲染层
↓
渲染层更新视图

对应到代码:

1
2
3
4
5
6
7
8
9
10
11
Page({
data: {
count: 0
},

handleTap() {
this.setData({
count: this.data.count + 1
})
}
})

看起来只是加一。

但底层不是:

1
JS 直接改 DOM

而是:

1
JS 改数据 → 框架通信 → 渲染层更新

这就是小程序和普通 Web 最核心的差异之一。


七、小游戏和小程序有什么不同

小游戏和小程序都运行在微信宿主环境里,也都可以调用微信提供的能力。

但它们的核心目标不同。

小程序偏向:

1
页面 + 组件 + 数据驱动 UI

小游戏偏向:

1
Canvas / WebGL + 游戏循环 + 实时渲染

可以简单对比:

维度 小程序 小游戏
主要形态 页面应用 游戏应用
UI 描述 WXML / WXSS Canvas / WebGL
更新方式 setData 驱动视图 游戏循环驱动画面
重点 页面渲染、组件、业务流程 帧率、资源、渲染、输入
常见引擎 原生组件 / 框架 Cocos、Laya、Three.js、Pixi 等

小游戏更接近:

1
微信宿主里的游戏运行时

它通常不是靠 setData 更新画面,而是靠:

1
2
3
4
5
6
7
function loop() {
update()
render()
requestAnimationFrame(loop)
}

loop()

也就是:

1
2
每一帧更新状态
每一帧重绘画面

这和小程序页面模型完全不同。


八、小游戏的基本运行链路

小游戏可以先这样理解:

1
2
3
4
5
6
7
8
9
游戏 JS
↓
微信小游戏基础库
↓
Canvas / WebGL 渲染接口
↓
微信客户端 Native 宿主
↓
系统图形能力

一次游戏帧大概是:

1
2
3
4
5
6
7
8
9
输入事件
↓
更新游戏状态
↓
执行碰撞 / 动画 / 物理 / AI
↓
绘制 Canvas / WebGL
↓
等待下一帧

所以小游戏优化关注的是:

  • 帧率是否稳定
  • 图片和音频资源是否过大
  • 首包是否过大
  • 是否频繁创建对象导致 GC
  • Canvas 绘制是否过重
  • WebGL draw call 是否太多

而小程序页面优化更关注:

  • setData 是否频繁
  • 数据包是否过大
  • 页面节点是否过多
  • 自定义组件是否过深
  • 页面切换是否慢
  • 分包和预加载是否合理

九、uni-app、Taro 这类框架站在哪一层

学习小程序底层逻辑时,很容易遇到另一个问题:

如果我用的是 uni-app 或 Taro,还需要理解原生小程序底层吗?

答案是:

1
需要。

因为 uni-app、Taro 这类框架不是替代微信运行时。

它们更像是:

1
写法转换层 + 跨端适配层

大致位置是:

1
2
3
4
5
6
7
8
9
10
11
Vue / React 风格源码
↓
uni-app / Taro 编译与运行时适配
↓
微信小程序产物
↓
微信基础库
↓
逻辑层 / 渲染层 / JSBridge
↓
微信客户端 Native

也就是说,不管你上层写的是 Vue、React、Composition API 还是 JSX,最终在微信小程序里仍然要落到微信那套模型:

1
2
3
4
5
6
WXML / WXSS / JS / JSON
setData
生命周期
事件系统
分包
微信基础库能力

1. uni-app 的位置

uni-app 更接近:

1
用 Vue 语法写多端应用

你写:

1
2
3
<template>
<view>{{ title }}</view>
</template>

编译到微信小程序后,会变成微信能识别的页面结构和逻辑。

可以这样理解:

1
2
3
4
5
uni-app 源码
↓
编译成微信小程序代码
↓
运行在微信小程序双线程模型里

uni-app 解决的是:

  • 多端统一写法
  • Vue 语法体验
  • 条件编译
  • 跨端组件和 API 适配

但它不能消除小程序底层限制。

比如:

  • 频繁更新数据依然有通信成本
  • 大量节点依然会影响渲染层
  • 分包策略依然重要
  • 微信能力依然受权限和宿主限制

2. Taro 的位置

Taro 更接近:

1
用 React / 类 React 写法开发多端应用

你可能写:

1
2
3
4
5
function Page() {
const [count, setCount] = useState(0)

return <View>{count}</View>
}

但最终在微信小程序里,它仍然要适配到小程序的组件、事件、生命周期和数据更新机制。

可以这样理解:

1
2
3
4
5
6
7
React 风格源码
↓
Taro 编译和运行时
↓
微信小程序页面
↓
微信基础库运行

Taro 解决的是:

  • React 风格开发体验
  • 多端编译
  • JSX 到小程序模板的转换
  • React 状态模型到小程序更新模型的适配

但底层仍然离不开:

1
小程序逻辑层 / 渲染层 / Native 通信

3. 框架不会改变底层约束

不管是 uni-app 还是 Taro,都不能让小程序变成真正的浏览器网页。

也不能让你绕过:

1
2
3
4
5
逻辑层和渲染层分离
setData / 数据同步成本
微信基础库能力边界
包体积限制
平台差异

所以学习顺序应该是:

1
2
3
先理解微信小程序底层模型
再理解 uni-app / Taro 如何适配这个模型
最后再学跨端框架的工程化能力

这样你遇到问题时才不会只停留在:

1
是不是框架 bug?

而是能继续向下判断:

1
2
3
4
5
是框架编译问题?
是运行时适配问题?
是 setData 通信问题?
是微信基础库限制?
还是平台差异?

4. 小游戏框架也类似

小游戏里的 Cocos、Laya、Pixi、Three.js 也可以放到同一个思路里理解。

它们不是替代微信小游戏运行时,而是站在微信小游戏提供的 Canvas / WebGL 能力之上。

大致是:

1
2
3
4
5
6
7
8
9
游戏引擎代码
↓
引擎运行时
↓
Canvas / WebGL
↓
微信小游戏基础库
↓
微信客户端 Native

所以小游戏框架解决的是:

  • 场景管理
  • 精灵系统
  • 动画
  • 资源管理
  • 碰撞
  • 音频
  • 渲染抽象

但底层约束仍然包括:

  • 帧率
  • 包体积
  • 资源加载
  • 内存
  • WebGL / Canvas 性能
  • 微信小游戏宿主能力边界

十、学习这个专栏的主线

这个专栏建议按下面链路展开:

1. 运行时总览

也就是本文。

先理解:

1
2
微信客户端是宿主
小程序 / 小游戏是运行在宿主里的受控应用

2. 小程序双线程架构

重点讲:

  • 逻辑层
  • 渲染层
  • Native 中转
  • 事件链路
  • setData 链路
  • 为什么不能直接操作 DOM

3. JSBridge 和原生能力

重点讲:

  • wx API 为什么能调用原生能力
  • JSBridge 的请求 / 响应模型
  • 回调、Promise、事件订阅
  • 权限和安全边界

4. 页面渲染和组件系统

重点讲:

  • WXML 怎么变成视图
  • WXSS 的限制
  • 自定义组件
  • 页面生命周期
  • 组件生命周期
  • 数据绑定

5. 小程序包加载机制

重点讲:

  • 主包
  • 分包
  • 独立分包
  • 预下载
  • 首屏性能
  • 冷启动和热启动

6. 小游戏运行时

重点讲:

  • Canvas
  • WebGL
  • 游戏循环
  • 资源加载
  • 输入事件
  • 音频
  • 开放数据域

7. 性能优化

重点讲:

  • 小程序 setData 优化
  • 页面节点优化
  • 分包优化
  • 小游戏帧率优化
  • 资源压缩
  • 渲染管线优化

8. 跨端框架和工程化

重点讲:

  • uni-app 如何编译到小程序
  • Taro 如何适配小程序运行时
  • React / Vue 状态更新如何映射到底层更新
  • 跨端组件的能力边界
  • 条件编译
  • 多端差异处理
  • 框架问题如何向下定位到底层链路

十一、第一阶段应该重点掌握什么

刚开始不需要急着学全部 API。

先把这几个问题搞懂:

1
2
3
4
5
6
1. 小程序为什么分逻辑层和渲染层?
2. setData 为什么有成本?
3. wx API 为什么能调用原生能力?
4. 小程序和普通 Web 的区别是什么?
5. 小游戏为什么更接近 Canvas / WebGL 运行时?
6. uni-app / Taro 这类框架为什么仍然绕不开小程序底层模型?

如果这几个问题能讲清楚,后面学登录、支付、分享、分包、性能优化都会顺很多。


🧠 一句话总结

微信小程序和小游戏的底层逻辑,核心不是 API,而是运行时链路:

1
2
3
4
5
6
7
8
9
10
11
开发者代码
↓
微信基础库
↓
逻辑层 / 渲染层 / 游戏运行时
↓
JSBridge
↓
微信客户端 Native
↓
系统能力

小程序更像:

1
受控的页面应用运行时

小游戏更像:

1
运行在微信里的游戏引擎环境

uni-app、Taro 这类框架更像:

1
站在小程序运行时之上的跨端编译和适配层

理解了这条链路,才算真正开始理解微信生态里的前端。


🔗 延伸阅读