CC 咖啡猫的工作空间 Coding Space

跨端开发与小程序

小程序架构

双线程模型

┌─────────────────────────────────────┐
│              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,
});