上篇回顾:2.4-02 把 Vite 与 Webpack 从「构建工具解决什么问题」到「Vite 为什么快」一次讲透。本篇是前端章节最后一篇——小程序。小程序是国内特色生态,微信/支付宝/抖音各有自己的小程序。本篇讲透小程序的双线程模型、跨端方案选型、高频实战。
一、开篇:小程序为什么不是 H5
后端工程师第一次看小程序代码,会觉得「这不就是 HTML/CSS/JS 换了个名字吗」:
<!-- WXML(类 HTML) --><viewclass="container"><text>{{message}}</text><buttonbindtap="onTap">点击</button></view>/* WXSS(类 CSS) */.container{padding:20rpx;}// JSPage({data:{message:'Hello'},onTap(){this.setData({message:'Clicked'});}});但小程序不是 H5。最根本的区别:小程序跑在双线程模型里,不能直接操作 DOM。
二、双线程模型:渲染层与逻辑层分离
2.1 架构图
┌─────────────────┐ ┌─────────────────┐ │ 渲染层(WebView) │ │ 逻辑层(JsCore) │ │ │ │ │ │ WXML → 渲染 │ ←→ │ JS 业务逻辑 │ │ WXSS → 样式 │ 数据 │ setData 触发更新 │ │ │ 通信 │ │ └─────────────────┘ └─────────────────┘ ↑ ↑ └─────── 微信原生 ───────┘- 渲染层:多个 WebView 实例(每个页面一个),负责 UI 渲染
- 逻辑层:单个 JsCore 实例(iOS)或 V8(Android),跑 JS 逻辑
- 两者通过微信原生的 JSBridge 通信
2.2 为什么禁止直接操作 DOM
原因一:安全。如果 JS 能操作 DOM,就能注入恶意代码修改页面。双线程隔离后,JS 只能通过setData传递数据,不能直接改 UI。
原因二:一致性。不同平台(iOS/Android/PC)的 WebView 实现不一致。禁止操作 DOM 后,渲染完全由原生控制,保证多端一致。
原因三:性能。直接操作 DOM 会触发不可控的重排重绘。setData是批量数据传递,框架可以做最小化更新。
2.3 setData 的代价
// 每次 setData 都要从逻辑层发送数据到渲染层this.setData({message:'new'});// 跨线程通信// 不要频繁 setDatafor(leti=0;i<100;i++){this.setData({count:i});// 100 次跨线程通信!极慢}// 正确:合并一次this.setData({count:99});setData 的性能陷阱:
- 数据要序列化(JSON)再传输
- 数据量大时传输慢
- 频繁 setData 导致通信阻塞
2.4 牺牲了什么换来了什么
| 牺牲了 | 换来了 |
|---|---|
| 直接操作 DOM 的能力 | 安全性 |
| 实时 UI 操作(动画) | 多端一致性 |
| 复杂交互性能 | 性能可控(框架优化) |
三、原生小程序 vs 跨端方案
3.1 原生开发
每个平台用自己的语法:微信 WXML/WXSS/JS、支付宝 AXML/ACSS、抖音 TTML/TTSS。
优点:性能最好、能力最全。
缺点:一套代码只能跑一个平台。
3.2 跨端方案
| 方案 | 思路 | 代表 |
|---|---|---|
| uni-app | Vue 语法 → 编译到各端 | DCloud 出品 |
| Taro | React 语法 → 编译到各端 | 京东出品 |
| mpvue | Vue 语法 → 微信小程序 | 已停更 |
3.3 uni-app vs Taro
| 维度 | uni-app | Taro |
|---|---|---|
| 语法 | Vue3 | React/Vue3 |
| 生态 | 国内最大、插件市场丰富 | 京东出品、React 生态 |
| 跨端覆盖 | 微信/支付宝/抖音/H5/App | 微信/支付宝/抖音/H5/RN |
| 学习曲线 | 低(Vue 用户友好) | 中(React 用户友好) |
| 文档 | 中文完善 | 中文完善 |
选型建议:
- Vue 用户 + 国内项目 → uni-app
- React 用户 + 多端需求 → Taro
- 只做微信小程序 + 追求极致性能 → 原生
四、vant-weapp 组件库接入
// app.json{"usingComponents":{"van-button":"@vant/weapp/button/index","van-cell":"@vant/weapp/cell/index","van-field":"@vant/weapp/field/index"}}<!-- 页面 WXML --><van-buttontype="primary"bindtap="onSubmit">提交</van-button><van-fieldvalue="{{form.name}}"bind:change="onNameChange"label="姓名"/>五、高频实战
5.1 授权登录
Page({asynclogin(){// 1. 获取 codeconst{code}=awaitwx.login();// 2. 发送到后端换 openid/session_keyconstres=awaitwx.request({url:'https://api.example.com/auth/wx-login',method:'POST',data:{code}});// 3. 保存 tokenwx.setStorageSync('token',res.data.token);}});注意:wx.getUserProfile已废弃,新版本用wx.getUserInfo或引导用户手动填写。
5.2 分包加载
// app.json{"pages":["pages/index/index"],"subPackages":[{"root":"packageA","pages":["pages/detail/detail"]}]}主包 <2MB,分包 <2MB,总包 <20MB。大功能放分包,按需加载。
5.3 网络请求封装
// utils/request.jsconstBASE_URL='https://api.example.com';functionrequest(options){returnnewPromise((resolve,reject)=>{wx.request({url:BASE_URL+options.url,method:options.method||'GET',data:options.data,header:{'Authorization':wx.getStorageSync('token')||'','Content-Type':'application/json'},success:(res)=>{if(res.data.code===401){wx.redirectTo({url:'/pages/login/login'});reject(newError('未授权'));return;}resolve(res.data);},fail:reject});});}exportdefaultrequest;六、小程序 vs H5 vs App
| 维度 | 小程序 | H5 | App |
|---|---|---|---|
| 开发成本 | 低 | 低 | 高 |
| 性能 | 中 | 低 | 高 |
| 体验 | 接近原生 | 一般 | 原生 |
| 分发 | 微信生态 | URL 分享 | 应用商店 |
| 审核 | 需要 | 不需要 | 严格 |
| 推送 | 订阅消息 | 浏览器推送 | 系统推送 |
七、小程序的代码为什么相对"好逆"
小程序的 JS 逻辑打包后存在本地,反编译工具(如 unwx)能还原出源码。原因:
- 小程序包下载到本地,文件可提取
- JS 没有强混淆(不像 App 有 native 保护)
- WX 编译器做的转换有限
平台方的真实预期:小程序的安全重点在「服务端数据」而非「客户端代码」。客户端代码被看到影响不大——真正的数据保护在后端 API 鉴权。
八、小结表
| 概念 | 要点 |
|---|---|
| 双线程 | 渲染层 WebView + 逻辑层 JsCore |
| setData | 跨线程通信,要合并减少频率 |
| 跨端方案 | uni-app(Vue)/ Taro(React) |
| 授权登录 | wx.login → 后端换 token |
| 分包 | 主包 <2MB,按需加载 |