小程序技術領域,有幾個謎同樣的存在:微信的WXS、支付寶的SJS、百度的Filter。html
不少開發者都不明白爲何要造這種語言腳本的輪子出來,甚至不少開發者根本不知道它們的存在。前端
其實幾大小程序平臺創造它們,都是爲了解決性能問題,但不得不吐槽下,設計的實在是很難用,文檔也語焉不詳。vue
uni-app
支持將WXS
、SJS
、Filter
編譯到這3家小程序平臺,同時還在App和H5實現了WXS
的解析。爲何作這些事?也是爲了性能。react
好比uni-ui
組件庫中的swiperaction
組件,就是列表項向左滑動時拉出幾個擠壓式聯動的菜單按鈕,這種流暢的跟手動畫,正是藉助於WXS
機制實現的。git
WXS(WeiXin Script)是微信創造的一套腳本語言,它的官方說法是:「WXS 與 JavaScript 是不一樣的語言,有本身的語法,並不和 JavaScript 一致」。github
那微信爲什麼要脫離 JavaScript ,單首創造一套語言呢?這要從微信小程序的底層邏輯(運行環境)講起。web
小程序的運行環境分爲邏輯層和視圖層,分別由2個線程管理,其中:json
小程序在視圖層與邏輯層兩個線程間提供了數據傳輸和事件系統。這樣的分離設計,帶來了顯而易見的好處:小程序
但同時也帶來了明顯的壞處:微信小程序
什麼是須要頻繁通信的場景?最典型的例子就是用戶持續交互的狀況,好比觸摸、滾動等。咱們以側滑菜單爲例,假設在頁面上滑動A元素,要求B元素跟隨移動,一次滑動操做(touchmove)的響應過程以下:
一次 touchmove 的響應須要通過 視圖層、Native、邏輯層三者之間2個完整來回的通訊,通訊的耗時開銷較大,用戶的交互就會出現延時卡頓的狀況。
除了滾動、拖動交互外,在for循環裏對數據作格式修改,也會形成邏輯層和視圖層頻繁通信。
其實這類通訊損耗問題,在業內由來已久,react native和weex都有相似問題,weex提供了bindingx來解決。
但對於小程序來說,這類問題解決起來更容易。由於其實視圖層的webview,是有js環境的,只不過過去不給開發者開放。
若是在視圖層的js直接處理滾動或拖動交互、直接處理數據格式,就能避免大量通訊損耗。
但對於小程序平臺而言,大量開放webview裏的js編寫,違反了它的初衷,好比開發者會直接操做dom,影響性能體驗。因此小程序平臺提出一種新規範,限制webview裏可運行的js的能力。這就是wxs、sjs、filter的由來。
從本質來說,wxs、sjs、filter是一種被限制過的、運行在視圖層webview裏的js。它並非真的發明了一種新語言。
WXS具有以下特徵:
class
和style
,或者對數據進行格式化。要修改邏輯層的數據,須要經過 callMethod,傳遞參數給邏輯層故能夠得出WXS的適用場景,主要包括:
<wxs module="m1">
//首字母大寫
var capitalize = function(value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
module.exports = {
capitalize: capitalize
}
</wxs>
<view class="content">
<view class="text-area">
<!-- title 爲當前頁面 data 中定義的初始數據 -->
<text class="title">{{m1.capitalize(title)}}</text>
</view>
</view>
複製代碼
uni-app
遵循Vue單文件組件(SFC)規範,組件/樣式/腳本是寫在一個.vue
文件中的,但微信小程序是多文件分離(wxml/wxss/js/json)的,因此在微信端的主要工做是擴展vue-template-compiler
,解析template/style/script
節點,並正確生成到對應的wxml/wxss/js
文件中,具體編譯工做以下圖:
Tips-1:關於<wxs>
標籤重構爲<script lang="wxs">
的說明:
因.vue
文件中的<wxs>
標籤及內嵌WXS代碼,在主流前端開發工具(vscode/HBuilderX等)中,均沒法實現語法提示、代碼高亮及格式化,故uni-app
將<wxs module="m1">
重構爲<script module="m1" lang="wxs">
,便捷實現了語法提示、代碼高亮等,以下爲vscode/HBuilderX中對於<wxs>
標籤重構先後的代碼高亮對比,明顯重構爲<script lang="wxs">
後,開發體驗更佳:
Tips-2:鑑於Vue的自定義標籤規範,咱們建議將<wxs>
(<script lang="wxs">
)和template
平級編寫
編譯器的具體解析擴展工做,這裏不詳述,僅給出wxs
生成的示例代碼,讓你們有個直觀理解:
createFilterTag (filterTag, {
content,
attrs
}) {
content = content.trim()
if (content) { //<wxs>標籤內直接編寫 wxs 代碼
return `<${filterTag} module="${attrs.module}">
${content}
</${filterTag}>`
} else if (attrs.src) { //外聯 .wxs 文件
return `<${filterTag} src="${attrs.src}" module="${attrs.module}"></${filterTag}>`
}
}
複製代碼
在保證編譯正確的狀況下,微信小程序運行時會正確解析並執行WXS
腳本,框架runtime
無需干預。
下面的gif顯示的內容,是藉助 WXS 實現的一個swipeaction
組件示例,列表項向左滑動時拉出幾個擠壓式聯動的菜單按鈕,跟手動畫、回彈動畫都很天然流暢。
這裏簡單給出主要實現思路:
<template>
<view class="uni-swipe_content">
<!-- 可滑動的菜單項容器,綁定touch事件 -->
<view :data-position="pos" class="move-hock"
@touchstart="swipe.touchstart" @touchmove="swipe.touchmove" @touchend="swipe.touchend" @change="change">
<view class="uni-swipe_box">
<slot />
</view>
<view class="uni-swipe_button-group move-hock">
<!-- 滑動後,右側擠壓式的聯動菜單按鈕-->
<view v-for="(item,index) in options" :data-button="btn" :key="index" class="button-hock">
{{ item.text }}
</view>
</view>
</view>
</view>
</template>
<script module="swipe" lang="wxs" src="./index.wxs"></script>
複製代碼
function touchstart(e, ins) {
//記錄開始位置及動畫狀態
var pageX = e.touches[0].pageX;
....
}
function touchmove(e, ownerInstance) {
var instance = e.instance;
var pageX = e.touches[0].pageX;//獲取當前移動位置
//計算偏移位置
var x = Math.max(-instance.getState().position[1].width, Math.min((value), 0));
//設置左側元素移動位置
instance.setStyle({transform: 'translateX(' + x + 'px)'})
//循環右側擠壓式聯動菜單
var btnIns = ownerInstance.selectAllComponents('.button-hock');
for (var i = 0; i < btnIns.length; i++) {
...
//設置每一個聯動菜單的移動位置
btnIns[i].setStyle({transform: 'translateX(' + (arr[i - 1] + value * (arr[i - 1] / position[1].width)) + 'px)'})
...
}
}
function touchend(e, ownerInstance) {
var instance = e.instance;
var state = instance.getState()
//根據當前移動位置,實現菜單項的自動展開或回彈
move(state.left, -40, instance, ownerInstance)
}
複製代碼
該示例的完整源碼參考github
在這段代碼中,響應手勢並移動菜單,是在視圖層直接完成的。而不用wxs的傳統寫法,實現這個功能就會很卡。
在持續的拖動過程當中,視圖層和邏輯層不停交互通訊,沒法作到跟手的順滑。
雖然咱們瞭解了wxs的原理,但老實講,wxs挺難用的,直到如今,大多數開發者仍然不會用它。比較合適的作法,仍是一些框架的做者對它進行封裝。uni-app
提供的uni-ui
組件庫,就是這樣作的,開發者只須要按標準vue組件的方式去引用uni ui
的swiperaction
組件,就能獲得流暢的滑動跟手菜單。
uni-app
的App端也是一個小程序引擎,爲了在App端實現流暢的跟手拖動,也實現和兼容了wxs。
其實H5平臺倒不存在邏輯層和視圖層通信折損的問題,但爲了平臺兼容性拉齊,uni-app
在H5端也實現了wxs機制。
這樣編寫wxs代碼,在uni-app
中可同時運行在App端、H5端、微信小程序端。
百度小程序的Filter過濾器和支付寶小程序的SJS,成熟度還比較低,目前只能處理基本的數據格式過濾,還不能響應touch等交互事件。
至於頭條和QQ小程序,還不支持類WXS機制。
期待其餘小程序平臺儘快補齊這個重要功能,實現體驗的提高。
uni-app
目前也支持單獨編寫百度小程序的Filter過濾器和支付寶小程序的SJS,這兩種腳本沒法跨多端,僅支持自有平臺。開發者若需使用,可分別編寫wxs/filter/sjs
腳本,而後依次經過script
引用,uni-app
編譯器會根據目標平臺,分別編譯發行,以下爲示例代碼: 示例代碼要有條件編譯
<!-- App/H5/微信小程序平臺調用wxs腳本 -->
<script module="utils" lang="wxs" src="./utils.wxs"></script>
<!-- 百度小程序平臺調用filter.js腳本 -->
<script module="utils" lang="filter" src="./utils.filter.js"></script>
<!-- 支付寶小程序平臺調用sjs腳本 -->
<script module="utils" lang="sjs" src="./utils.sjs"></script>
複製代碼
用運行在視圖層的js解決通信阻塞,可能不少人都沒意識到。但願本文能給你們解惑,解開WXS之謎。
其實小程序的性能體驗優化,仍然有大量空間。DCloud團隊在這個領域研究了6年,清楚小程序技術架構的優點,也清楚當前的問題。咱們會繼續分享這些問題及對應的解決方案,爲小程序產業發展貢獻力量。
本文涉及的uni-ui
的swiperaction
組件,代碼開源在github.com/dcloudio/un…,uni-app
框架代碼開源在 github.com/dcloudio/un…,歡迎你們 star 或提交 pr。