Page 1 of 1

智能小程序性能优化 03 - SJS使用

Posted: 2022年 Nov 7日 18:31
by Muzzzhi

SJS是什么

SJS(safe/subset javascript) 是小程序的一套脚本语言,结合 tyml,可以构建出页面的结构。

那小程序为何要脱离 JavaScript ,单独创造一套语言呢?这要从智能小程序的底层逻辑(运行环境)讲起。

小程序的运行环境分为逻辑层和视图层,分别由2个线程管理,其中:

TYML 模板和 TYSS 样式工作在视图层,界面使用 WebView 进行渲染。
JavaScript代码工作在逻辑层,运行在JsCore或v8等JS引擎里。

小程序在视图层与逻辑层两个线程间提供了数据传输和事件系统。这样的分离设计,带来了显而易见的好处:
逻辑和视图分离,即使业务逻辑计算非常繁忙,也不会阻塞渲染和用户在视图层上的交互

但同时也带来了明显的坏处:
视图层(webview)中不能运行JS,而逻辑层JS又无法直接修改页面DOM,数据更新及事件系统只能靠线程间通讯,但跨线程通信的成本极高,特别是需要频繁通信的场景。

从本质来讲,SJS是一种被限制过的、运行在视图层webview里的js。它并不是真的发明了一种新语言。

SJS特征及适用场景

SJS具备如下特征:

SJS是可以在视图层(webview)中运行的JS
SJS无法直接修改业务数据,仅能设置当前组件的class和style,或者对数据进行格式化。
SJS是被限制过的JavaScript,可以进行一些简单的逻辑运算

故可以得出SJS的适用场景,主要包括:
数据格式处理,比如文本、日期格式化,或者国际化。

SJS响应事件

背景信息

如有频繁用户交互,在小程序上表现是比较卡顿的。例如,页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动,movable-view 就是一个典型的例子。一次 touchmove 事件的响应过程为:

  1. touchmove 事件从视图层(Webview)抛到逻辑层(App Service)。

  2. 逻辑层(App Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置。

一次 touchmove 的响应需要经过 2 次逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。此外,setData 渲染也会阻塞其它脚本执行,导致整个用户交互的动画过程出现延迟。

#实现方案

本方案基本的思路是减少通信次数,让事件在视图层(Webview)响应。小程序的框架分为视图层(Webview)和逻辑层(App Service)。这样分层的目的是管控,开发者的代码只能运行在逻辑层(App Service),而这个思路就必须要让开发者的代码运行在视图层(Webview),如下图所示的流程:

sjs.png

使用 SJS 函数用来响应小程序事件,目前只能响应内置组件的事件,不支持自定义组件事件。SJS 函数除了纯逻辑的运算,还可以通过封装好的 ComponentDescriptor 实例来访问以及设置组件的class 和样式。对于交互动画,设置 styleclass 足够了。SJS 函数的例子如下:

Code: Select all

const sjsFunction = function (event, ownerInstance) {
  const instance = ownerInstance.selectComponent('.classSelector'); // 返回组件的实例
  instance.setStyle({
    'font-size': '14px',
  });
  instance.getDataset();
  instance.setClass(className);
  // ...
  return false; // 不往上冒泡,相当于同时调用了 stopPropagation 和 preventDefault
};

其中,入参 event 是小程序事件对象基础上多了 event.instance,来表示触发事件的组件的 ComponentDescriptor 实例。ownerInstance 表示的是触发事件的组件所在组件的 ComponentDescriptor 实例。如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。

ComponentDescriptor 的定义如下:

方法参数描述
selectComponentselector 对象返回组件的 ComponentDescriptor 实例。
selectAllComponentsselector 对象数组返回组件的 ComponentDescriptor 实例数组。
setStyleObject/string设置组件样式。设置的样式优先级高于组件 tyml 里面定义的样式。不能设置最顶层页面的样式。
addClass/removeClass/hasClassstring设置组件的 class。设置的 class 优先级高于组件 tyml 里面定义的 class。不能设置最顶层页面的 class
getDataset返回当前组件或者页面的 dataset 对象。
callMethod(funcName:string, args:object)调用当前组件或者页面在逻辑层(App Service)定义的函数。funcName 表示函数名称,args 表示函数的参数。
requestAnimationFrameFunction和原生 requestAnimationFrame 一样。用于设置动画。
getState返回一个 object 对象。当局部变量需要存储起来,以便后续使用的时候,可以用这个方法。
triggerEvent(eventName, detail)和组件的 triggerEvent 一致。
getComputedStyleArray.<string>参数与 SelectorQuery 的 computedStyle 一致。
setTimeout(Function, Number)与原生 setTimeout 一致。用于创建定时器。
clearTimeoutNumber与原生 clearTimeout 一致。用于清除定时器。
getBoundingClientRect返回值与 SelectorQuery 的 boundingClientRect 一致。

SJS 运行在视图层(Webview),里面的逻辑毕竟能做的事件比较少,需要有一个机制和逻辑层(App Service)开发者的代码通信。上面的 callMethod 是 SJS 里面调用逻辑层(App Service)开发者的代码的方法,而 SjsPropObserver 是逻辑层(App Service)开发者的代码调用 SJS 逻辑的机制。

使用方法

tyml 定义事件:

Code: Select all

<sjs module="test" src="./test.sjs"></sjs>

<view change:prop="{{test.propObserver}}" prop="{{propValue}}" bind:touchmove="{{test.touchmove}}" class="movable"></view>

上面的 change:prop(属性前面带 change: 前缀)是在 prop 属性被设置的时候触发 SJS 函数,值必须用 {{}} 括起来。类似 Component 定义的 properties 里面的 observer 属性,在 setData({propValue: newValue}) 调用之后会触发。

注意:SJS 函数必须用 {{}} 括起来。当 prop 的值被设置时,SJS 函数就会触发,而不只是值发生改变。所以在页面初始化时,会调用一次 SjsPropObserver 的函数。

SJS 文件 test.sjs 里面定义并导出事件处理函数和属性改变触发的函数:

Code: Select all

// event:事件对象。
// ownerInstance:表示的是触发事件的组件所在组件的 ComponentDescriptor 实例。如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。
const touchmove = function (event, ownerInstance) {
  console.log('log event', JSON.stringify(event));
};

// newValue:新值。
// oldValue:旧值。
// ownerInstance:表示的是触发事件的组件所在组件的 ComponentDescriptor 实例。如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。
// instance:表示触发事件的组件的 ComponentDescriptor 实例。
const propObserver = function (newValue, oldValue, ownerInstance, instance) {
  console.log('prop observer', newValue, oldValue);
};

export default {
  touchmove: touchmove,
  propObserver: propObserver,
};

Re: 智能小程序性能优化 03 - SJS使用

Posted: 2022年 Nov 7日 18:35
by TheThingX

沙发是我的,别抢