小程序架构

小程序双线程架构模型及通信机制

问题

描述小程序的架构设计,解释为什么采用这种架构。

解答

双线程架构

小程序采用双线程模型,将渲染层和逻辑层分离:

┌─────────────────────────────────────────────────────┐
│                      视图层                          │
│                   (Render Thread)                    │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐             │
│  │ WebView │  │ WebView │  │ WebView │   ...       │
│  │ (页面1) │  │ (页面2) │  │ (页面3) │             │
│  └─────────┘  └─────────┘  └─────────┘             │
└──────────────────────┬──────────────────────────────┘

                       │ setData / 事件

┌─────────────────────────────────────────────────────┐
│                    Native 层                         │
│         (JSBridge / 微信客户端)                      │
└──────────────────────┬──────────────────────────────┘

                       │ 数据 / 回调

┌─────────────────────────────────────────────────────┐
│                     逻辑层                           │
│                  (Logic Thread)                      │
│              ┌─────────────────┐                    │
│              │    JsCore /     │                    │
│              │     V8 引擎     │                    │
│              └─────────────────┘                    │
│                  App Service                        │
└─────────────────────────────────────────────────────┘

各层职责

// 逻辑层 - 运行在 JsCore/V8 中
// 负责业务逻辑、数据处理、API 调用
Page({
  data: {
    list: []
  },
  
  onLoad() {
    // 调用微信 API(通过 Native 层)
    wx.request({
      url: 'https://api.example.com/data',
      success: (res) => {
        // 通过 setData 将数据发送到渲染层
        this.setData({ list: res.data })
      }
    })
  },
  
  // 接收渲染层的事件
  handleTap(e) {
    console.log('点击事件', e)
  }
})
<!-- 渲染层 - 运行在 WebView 中 -->
<!-- 负责页面渲染、用户交互 -->
<view class="container">
  <view 
    wx:for="{{list}}" 
    wx:key="id"
    bindtap="handleTap"
  >
    {{item.name}}
  </view>
</view>

通信机制

// setData 的工作流程
this.setData({ count: 1 })

// 1. 逻辑层:数据序列化为 JSON 字符串
// 2. Native 层:通过 JSBridge 传输数据
// 3. 渲染层:反序列化,执行 diff,更新 DOM

// 性能优化:减少 setData 调用次数和数据量
// 错误示范
this.setData({ a: 1 })
this.setData({ b: 2 })
this.setData({ c: 3 })

// 正确做法
this.setData({ a: 1, b: 2, c: 3 })

// 只更新需要的字段
this.setData({
  'list[0].name': 'new name',  // 路径更新
  'obj.key': 'value'
})

为什么采用双线程

// 1. 安全性 - 逻辑层无法直接操作 DOM
// 开发者无法执行以下操作:
document.getElementById('xxx')  // 不可用
window.location.href = 'xxx'    // 不可用

// 2. 性能 - 渲染和逻辑并行执行
// 逻辑层计算不会阻塞渲染
// 多个 WebView 可以预加载

// 3. 管控 - 平台可以控制 API 能力
// 所有能力都通过 Native 层提供
wx.request()      // 网络请求
wx.getLocation()  // 位置信息
wx.scanCode()     // 扫码

与传统 Web 的对比

特性小程序传统 Web
线程模型双线程单线程
DOM 操作不可直接操作可直接操作
渲染引擎WebView浏览器
JS 引擎JsCore/V8浏览器内置
通信方式异步序列化同步

关键点

  • 双线程架构:渲染层(WebView)和逻辑层(JsCore)分离运行
  • 通信机制:通过 Native 层的 JSBridge 进行数据传输,setData 是异步的
  • 安全管控:逻辑层无法直接操作 DOM,所有能力由平台提供
  • 性能代价:跨线程通信需要序列化,频繁 setData 会影响性能
  • 每个页面对应一个 WebView,支持多页面预加载