微信的 WXS: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxs/
https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
支付宝的 SJS: https://docs.alipay.com/mini/framework/sjs
百度的 Filte: https://smartprogram.baidu.com/docs/develop/framework/view_filter/
微信为何要创造 WXS
WXS(WeiXin Script)是微信创造的一套脚本语言,它的官方说法是:“WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致”。
小程序的运行环境分为逻辑层和视图层,分别由 2 个线程管理,其中:
- WXML 模板和 WXSS 样式工作在视图层,界面使用 WebView 进行渲染。
- JavaScript 代码工作在逻辑层,运行在 JsCore 或 v8 里。
小程序在视图层与逻辑层两个线程间提供了数据传输和事件系统。这样的分离设计,带来了显而易见的好处:
- 逻辑和视图分离,即使业务逻辑计算非常繁忙,也不会阻塞渲染和用户在视图层上的交互。
但同时也带来了明显的坏处:
- 视图层(webview)中不能运行 JS,而逻辑层 JS 又无法直接修改页面 DOM,数据更新及事件系统只能靠线程间通讯,但跨线程通信的成本极高,特别是需要频繁通信的场景。
什么是需要频繁通讯的场景?最典型的例子就是用户持续交互的情况,比如触摸、滚动等。我们以侧滑菜单为例,假设在页面上滑动 A 元素,要求 B 元素跟随移动,一次滑动操作(touchmove)的响应过程如下:
- touchmove 事件从视图层(Webview)传递到逻辑层,中间会由微信客户端(Native)做中转。
- 逻辑层处理 touchmove 事件,计算需移动的位置,然后再通过 setData 传递到视图层,中间同样会由微信客户端(Native)做中转。
一次 touchmove 的响应需要经过 视图层、Native、逻辑层三者之间 2 个完整来回的通信,通信的耗时开销较大,用户的交互就会出现延时卡顿的情况。
对于小程序来讲,这类问题解决起来更容易。其实视图层的 webview,是有 js 环境的,只不过过去不给开发者开放。
如果在视图层的 js 直接处理滚动或拖动交互、直接处理数据格式,就能避免大量通信损耗。
但对于小程序平台而言,大量开放 webview 里的 js 编写,违反了它的初衷,比如开发者会直接操作 dom,影响性能体验。所以小程序平台提出一种新规范,限制 webview 里可运行的 js 的能力。这就是 wxs、sjs、filter 的由来。
从本质来讲,wxs、sjs、filter 是一种被限制过的、运行在视图层 webview 里的 js。
WXS 特征及适用场景
WXS 具备如下特征:
- WXS 是可以在视图层(webview)中运行的 JS。
- WXS 无法修改业务数据,仅能设置当前组件的class和style。
- WXS 是被限制过的 JavaScript,可以进行一些简单的逻辑运算。
- WXS 可以监听 touch 事件,处理滚动、拖动交互。
故可以得出 WXS 的适用场景,主要包括:
- 用户交互频繁、仅需改动组件样式(比如布局位置),无需改动数据内容的场景,比如侧滑菜单、索引列表、滚动渐变等。
- 纯粹的逻辑计算,比如文本、日期格式化,通过 WXS 可以模拟实现 Vue 框架的过滤器, 如下是一个通过 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>
其中入参 event 是小程序事件对象基础上多了 event.instance 来表示触发事件的组件的 ComponentDescriptor 实例。ownerInstance 表示的是触发事件的组件所在的组件的 ComponentDescriptor 实例,如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。<view class="wrap">
<image class="inner" data-id="1" src="1.jpg" @touchmove="drag.touchmove">
</view>
<wxs module="drag">
var touchmove = function(e) {
var instance = e.instance
var x = e.touches[0].pageX
var y = e.touches[0].pageY
instance.setStyle({
left: x+'px',
top: y+'px'
})
}
module.exports = {
touchmove:touchmove
}
</wxs>
<style>
.wrap{
position:relative
}
.inner{
position:absolute;
top:0;
left:0
}
</style>
ComponentDescriptor的定义如下:
方法 | 参数 | 描述 | 最低版本 |
---|---|---|---|
selectComponent | selector对象 | 返回组件的 ComponentDescriptor 实例。 | |
selectAllComponents | selector对象数组 | 返回组件的 ComponentDescriptor 实例数组。 | |
setStyle | Object/string | 设置组件样式,支持rpx。设置的样式优先级比组件 wxml 里面定义的样式高。不能设置最顶层页面的样式。 | |
addClass/removeClass/hasClass | string | 设置组件的 class。设置的 class 优先级比组件 wxml 里面定义的 class 高。不能设置最顶层页面的 class。 | |
getDataset | 无 | 返回当前组件/页面的 dataset 对象 | |
callMethod | (funcName:string, args:object) | 调用当前组件/页面在逻辑层(App Service)定义的函数。funcName表示函数名称,args表示函数的参数。 | |
requestAnimationFrame | Function | 和原生 requestAnimationFrame 一样。用于设置动画。 | |
getState | 无 | 返回一个object对象,当有局部变量需要存储起来后续使用的时候用这个方法。 | |
triggerEvent | (eventName, detail) | 和组件的triggerEvent 一致。 |
|
getComputedStyle | Array. |
参数与 SelectorQuery 的 computedStyle 一致。 |
2.11.2 |
setTimeout | (Function, Number) | 与原生 setTimeout 一致。用于创建定时器。 | 2.14.2 |
clearTimeout | Number | 与原生 clearTimeout 一致。用于清除定时器。 | 2.14.2 |
getBoundingClientRect | 无 | 返回值与 SelectorQuery 的 boundingClientRect 一致。 |
2.14.2 |