跨端开发与小程序
小程序架构
双线程模型
┌─────────────────────────────────────┐
│ Native 层 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 渲染层 │ │ 逻辑层 │ │
│ │ (WebView) │ │ (JSCore) │ │
│ │ │ │ │ │
│ │ WXML → DOM │ │ JS 执行 │ │
│ │ CSS 样式 │ │ setData │ │
│ │ │ │ 事件回调 │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ └─────────────────┘ │
│ Native 桥接(JSBridge) │
└─────────────────────────────────────┘
| 层级 |
运行环境 |
职责 |
限制 |
| 渲染层 |
WebView(每个页面独立) |
渲染 WXML 到 DOM、应用 CSS 样式 |
不能执行 JS 逻辑 |
| 逻辑层 |
JSCore / V8(全局唯一) |
执行业务 JS、处理事件、setData |
不能操作 DOM |
| Native 桥接 |
JSBridge |
双向通信、组件渲染、网络请求 |
序列化开销 |
数据传递
逻辑层 setData(data) ──► JSON 序列化 ──► Native 桥 ──► JSON 解析 ──► 渲染层更新 DOM
setData 优化
// ❌ 差:频繁调用大对象
// 每次 setData 会序列化整个对象,Native 桥性能瓶颈
this.setData({
list: newList // 整个大数组重新发送
});
// ✅ 好:路径更新 + 合并
this.setData({
['list[0].name']: '新名字', // 只传变化的字段
['list[3].status']: 1
});
// ✅ 合并多次 setData(框架自动批处理)
// Vue/React 框架内由框架合并,原生建议用 nextTick 合并
| 优化技巧 |
说明 |
| 路径更新 |
用 this.setData({ 'a.b.c': value }) 代替整对象赋值 |
| 合并请求 |
将多次 setData 合并为一次 |
| 避免频繁调用 |
使用节流控制高频事件内的 setData |
| 减少数据量 |
只传视图需要的字段,不传整个对象 |
| 页面非可见时 |
页面由 wx.onHide 时暂停 setData |
Taro / uni-app
编译时 + 运行时架构
开发代码(React/Vue 语法)
│
▼
编译时 ──► 转译为对应小程序的 WXML/WXSS/JS
│
▼
运行时 ──► 在逻辑层模拟 DOM/BOM API(document.createElement 等)
│
▼
原生小程序代码
| 阶段 |
工作 |
技术细节 |
| 编译时 |
将 JSX/Vue 模板转译为 WXML |
Babel 插件 AST 转换 |
| 运行时 |
在 JSCore 中运行 DOM 模拟层 |
@tarojs/runtime 提供 DOM/BOM polyfill |
| 桥接 |
setData 封装 |
框架收集变更后统一 setData |
跨平台适配
// Taro 获取环境
import Taro from '@tarojs/taro';
const env = Taro.getEnv();
// 'WEAPP' | 'WEB' | 'RN' | 'H5' | 'SWAN' | 'ALIPAY' | 'QUICKAPP'
// 条件编译
/* #ifdef mp-weixin */
// 仅微信小程序运行的代码
wx.getSystemInfoSync();
/* #endif */
/* #ifdef h5 */
// 仅 H5 运行的代码
alert('h5');
/* #endif */
// 条件编译(uni-app)
// #ifdef H5
console.log('H5 only');
// #endif
// #ifdef MP-WEIXIN
console.log('Wechat only');
// #endif
Taro / uni-app 对比
| 维度 |
Taro 3+ |
uni-app |
| 框架支持 |
React / Vue / Preact |
Vue(主要)/ React(beta) |
| 编译模式 |
编译时转换 JSX/Vue 模板 |
编译时 + 运行时 |
| 平台支持 |
微信/阿里/百度/字节/QQ/快手/H5/RN |
微信/阿里/百度/字节/QQ/快手/H5/App |
| H5 支持 |
好(React 渲染) |
好(Vue 渲染) |
| 性能 |
运行时 DOM 模拟开销较大小程序 |
同为编译时 + 运行时 |
React Native
JS Bridge 原理
JS 线程 Bridge(异步队列) Native 线程
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ React 组件 │ │ │ │ │
│ Virtual DOM │──────►│ MessageQueue │──────►│ UIKit / View │
│ Diff → Patch │ │ JSON 序列化 │ │ 布局引擎 │
│ │◄──────│ {type:"create", │◄──────│ 事件/响应返回 │
│ │ │ props...} │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
| 架构 |
通信方式 |
特点 |
版本 |
| 旧架构(Bridge) |
异步 JSON 序列化(MessageQueue) |
批量异步,开销大 |
RN < 0.68 |
| 新架构(JSI) |
同步 C++ 直接调用(HostObject) |
无需序列化,直接内存共享 |
RN ≥ 0.68 |
JSI(JavaScript Interface):
- C++ 层直接暴露 Native 方法给 JS,跳过 JSON 序列化
- 支持同步调用(如
console.log / Date.now 等同步操作)
- 启动时无需一次性扫描所有模块,TurboModules 按需加载
Hermes 引擎
| 特性 |
说明 |
| AOT 编译 |
字节码预编译(非 JIT),安装包变大但启动更快 |
| 内存优化 |
比 V8/JSC 节省 30~40% 内存 |
| 启用 |
hermes_enabled => true in build.gradle / Podfile |
新架构(Fabric + TurboModules)
| 组件 |
旧架构 |
新架构(Fabric) |
| 渲染器 |
Shadow Tree → Yoga 布局 → UI 线程 |
C++ 统一 Fabric 渲染管线 |
| 线程模型 |
JS 线程 → Bridge(异步)→ Shadow → UI |
JSI 直接同步到 UI |
| 状态管理 |
setState 经过异步 bridge |
状态同步更新 |
| 事件 |
分批异步 |
同步处理 |
| 组件 |
旧架构 |
新架构(TurboModules) |
| Native 模块 |
启动时扫描所有模块(即使不使用) |
按需懒加载,只在 JS 调用时加载 |
| 通信 |
JSON 序列化 + 异步 |
JSI 直接对象引用 + 同步 |
Flutter
自绘引擎
Flutter 应用
│
▼
Flutter Framework(Dart)
│
▼
Engine(C++)
│
├── Skia(旧) / Impeller(新)
│ │
│ ▼
│ GPU 直接绘制(不依赖平台控件)
│
└── Platform Channels(调用原生 API)
为什么自绘: 不依赖 iOS / Android 的原生 UI 控件,同一套绘制代码在不同平台效果一致,消除平台差异。
| 渲染引擎 |
版本 |
特点 |
| Skia |
Flutter ≤ 3.16 默认 |
C++ 2D 图形库,已存在 20+ 年,首次绘制有性能抖动 |
| Impeller |
Flutter 3.16+ 默认(部分平台) |
预编译着色器,消除 Skia 的 jank(首次帧卡顿) |
三棵树
Widget(配置/不可变)
│
▼ createElement()
Element(实例/管理生命周期)
│
▼ mount()
RenderObject(布局/绘制/可变)
| 树 |
对象 |
作用 |
生命周期 |
| Widget |
Configuration |
描述 UI 的配置(类似 React 的 JSX) |
每次 rebuild 都重建 |
| Element |
实例 |
管理 Widget 与 RenderObject 的关系 |
在整个视图存活期间存在 |
| RenderObject |
布局/绘制 |
计算布局约束、处理绘制指令 |
随 Element 存在 |
// 一个 StatelessWidget 的生命周期:创建 Widget → 创建 Element → 创建 RenderObject → 布局 → 绘制
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: Colors.blue,
);
}
}
Dart 语言特性
| 阶段 |
模式 |
优势 |
| 开发 |
JIT + 热重载(Hot Reload) |
修改源码后秒级更新 UI,状态保持 |
| 发布 |
AOT 编译到机器码(ARM/x64) |
直接执行机器码,无 JIT 预热开销 |
Electron
主进程 + 渲染进程
┌────────────────────────────────────────┐
│ 主进程(Main Process) │
│ - 启动应用窗口(BrowserWindow) │
│ - 菜单、托盘、系统对话框 │
│ - 自动更新、原生 API │
│ - Node.js 全功能 │
└─────────────┬──────────────────────────┘
│ IPC(进程间通信)
┌─────────┴──────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ 渲染进程 │ │ 渲染进程 │
│ (Chromium)│ │ (Chromium)│
│ - 页面UI │ │ - 页面UI │
│ - 沙盒化 │ │ - 沙盒化 │
└──────────┘ └──────────┘
进程间通信 IPC
// 主进程 main.js
const { ipcMain, BrowserWindow } = require('electron');
ipcMain.handle('get-file', async (event, filePath) => {
return await fs.readFile(filePath, 'utf-8');
});
// 渲染进程 renderer.js
const { ipcRenderer } = require('electron');
const content = await ipcRenderer.invoke('get-file', '/path/file.txt');
| API |
用途 |
方向 |
ipcMain.handle / ipcRenderer.invoke |
请求-响应模式(Promise) |
Renderer → Main |
ipcMain.on / ipcRenderer.send |
单向消息 |
Renderer → Main |
mainWindow.webContents.send |
向渲染进程推送 |
Main → Renderer |
打包体积优化
| 手段 |
效果 |
| 共享 V8 Context |
Electron 7+ 多个渲染进程共享同一 V8 context,减少内存 |
| asar 归档 |
将所有源码打包到 .asar 文件中,减少文件数量,一定程度压缩 |
| 按需加载 Native 模块 |
避免在 main.js 顶部 require 所有模块 |
| electron-builder / electron-forge |
按平台裁剪,移除不需要的二进制文件 |
安全
| 配置 |
风险 |
推荐 |
contextIsolation: true |
隔离渲染进程与 Node.js 全局对象 |
✅ 必须启用 |
nodeIntegration: false |
渲染进程无法直接使用 Node.js API |
✅ 必须禁用 |
preload 脚本 |
通过 contextBridge 暴露受限 API |
✅ 使用预加载脚本 |
// 安全的 Electron 配置
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // 隔离上下文
nodeIntegration: false, // 禁用 Node
sandbox: true, // 沙盒模式
}
});
// preload.js - 通过 contextBridge 暴露安全 API
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('open-file'),
getVersion: () => process.versions.electron,
});