在平时的开发中,我们很少会主动去使用 sjs
,程序能够运行,不加班,已经是极好的事情了:)。但我们这个系列不可以,深入原理部分,必须对每一个细节都了若指掌,差之毫厘谬以千里。
sjs 定义
以下是官方定义:
SJS(safe/subset javascript)是小程序一套自定义脚本语言,可以在 AXML 中使用其构建页面结构。 SJS 是 JavaScript 语言的子集,与 JavaScript 是不同的语言,其语法并不与 JavaScript 一致,请勿将其等同于 JavaScript。
- sjs 中只支持使用 import、export 管理模块依赖。
- sjs 只能定义在 .sjs 文件中。然后在 axml 中使用
标签引入。 - sjs 可以调用其他 sjs 文件中定义的函数。
- sjs 是 JavaScript 语言的子集,请勿将其等同于 JavaScript。
- sjs 的运行环境和其他 JavaScript 代码是隔离的, sjs 中不能调用其他 JavaScript 文件中定义的函数,也不能调用小程序提供的 API。
- sjs 函数不能作为组件事件回调。
- sjs 不依赖于基础库版本,可以在所有版本小程序中运行。
- sjs 可以响应事件
简单理解:sjs
是js的子集,运行在受限的容器中,一般不能修改业务数据,可以响应事件。
深入分析 sjs
不同的报错
我们尝试在业务代码和 sjs
中使用 eval
函数,两者提示不一样:
// af-appx.worker.min.js
TypeError: eval is not a function
// VM194 index.html:3 Module build failed (from /snapshot/code-repo/out/target/bundle/node_modules/@ali/antcube-thread-loader/lib/cjs.js):
SyntaxError: identifier(eval) is disallowed in sjs
注意这里两个报错是不一样的!在af-appx.worker.min.js
中,eval 并全局指向了 undefined
, 在 sjs 中,eval
是不被运行使用。不被允许,并不是不存在的意思,也就是这个方法是存在的,只是我不让你使用。
双线程架构
小程序的架构中 js 一般在独立线程中执行,页面的渲染在 webview
中执行,两者通过 jsbridge
进行通信。那么 sjs
在哪里执行?
这一次我们使用miniu构建我们的小程序,我们分析小程序打包的代码:
目录结构
.
├── appConfig.json
├── assets
│ └── logo.png
├── index.html
├── index.js
├── index.worker.html
├── index.worker.js
└── manifest.json
index.html 中文件的加载顺序
<body>
<script src="index.js"></script>
<script>
window.bootstrapApp({
worker: 'index.worker.js?version=1627720007775a8392433130335686',
onReady: onReady,
});
</script>
页面在加载时候,会先加载 index.js
文件,然后启动 works,执行用户的业务代码。
我们的 sjs
代码被打入 index.js
中,这里我把 a:222
,改成了a=eval(333)
,页面正常显示。
index.sjs
应该并不是用户逻辑的代码,而是服务于webview
部分的代码,所有和webview 相关的代码都被放到了这里,它的运行环境就是 webview
。
我们继续尝试,a
改成 window.innerHeight
,页面运行成功,显示 595
高度,验证了我们的猜想,这里可以访问到完整的 webview
环境。
安全
小程序各种安全体系下,sjs
比较容易产生风险。
小程序打包需要执行 mini-pkg-builder
程序,我们在本地预览,远程调试时候,都是他在做小程序的打包工作,这是一个加密的 unix 可执行文件,尝试反编译,无果。
我们小程序上传时候,会在服务端的容器中执行 mini-pkg-builder
程序。保证了程序本身不会被用户影响,即使本地尝试修改这个文件,也不会影响到正式发布。
然而,攻与防从来都是相对的,这里会不会存在逻辑bug,就不得而知了,有趣的你是不是跃跃欲试了。