初在研究对移动网络传输进行功耗优化,在一次意外的监听网络传输包中截获了微信小程序的请求包,借此来窥探当下前端代码安全。
0x01 小程序分析
小程序包结构
Segment | Name | Length | Remark |
---|---|---|---|
Header | FirstMark | 1 byte | 0xBE 固定值 |
Edition | 4 bytes | 0 -> 微信分发到客户端 1 -> 开发者上传到微信后台 | |
IndexInfoLength | 4 bytes | 索引段的长度 | |
BodyInfoLength | 4 bytes | 数据段的长度 | |
LastMark | 1 byte | 0xED 固定值 | |
FileCount | 4 bytes | 文件数量 | |
Index | NameLength | 4 bytes | 文件名长度 |
Name | NameLength bytes | 文件名,长度为NameLength | |
FileOffset | 4 bytes | 文件在数据段位置 | |
FileSize | 4 bytes | 文件大小 | |
LOOP...... | |||
Data | Files Package...... |
包结构非常清晰,分为三个部分:
头信息,包含一些包的标识,版本定义等,包含了三个冗余字段 --- 索引段和数据段的长度应该是用于做校验,但实质上没有用(设计者觉得需要设计一些冗余字段来确保设计的完整性,防止解析的时候溢出,但实际工程实践中并没有起到相应的作用),文件数量应该是用于简化包解析过程,实际上知道了索引段长度或数据段长度中任何一个皆可推算出文件数量。
索引段,包含文件的元信息 --- 文件名以及文件位置(通过FileOffset和FileSize定位数据段中的文件)。如果从精简包的大小的角度来看,FileOffset和FileSize只需一个存在即可,但是这样解析包的难度就大大增加了,还是以工程实践为主。
数据段,将所有的文件罗列在一起。
由此可见,数据完全没有经过压缩或者加密,连包的签名信息也没有。这导致只能在制品流程上进行严格控制,例如在开发者上传代码过程中需要授信,必须经过审查,也一定得由授信平台进行代码分发等。这些都无关风月,毕竟App Store就是这种模式,但是......
如何拆解这种自定义文件格式呢?
对多个相同格式的文件进行对比,对大体结构有宏观的感觉,很容易发现一些固定的字段以及一些结构的长度。对于像小程序这种有软件本体的例子,还可以通过微量修改来观察文件的变化来找到文件结构和意义。
观察特殊形式,首先英文的字符串是很明显的,一般hex编辑器都自带字符串化窗口,如果发现常见的字符串,就可以继续去寻找字符串的边界,字符串在二进制文件里有两种储存方式:一种是不记录字符串的长度,读取字符串到0x00位置,另一种一定在某一个地方储存了这个字符串的长度,因此一旦得知了该字符串的内容,搜索该长度字段即可获取更多的信息。其次一些文件头也非常显眼,例如PNG[1]、ZIP[2]等通用标准文件格式都有固定的文件头,在小程序的自定义格式中很容易发现一些png、jpg等资源的文件头,因此可以定位数据区的位置。
对特定区域的二进制进行推理猜测,一般来说二进制文件里需要储存大量的offset和size的数据作为数据段的索引。offset相当于一个指针 - 索引文件在数据段的位置,工程实践中,大部分储存了offset的地方也会储存size字段,毕竟在解析文件的时候会方便很多,也可以防止校验数据出现指针越界。因此,一旦确认了文件中的数据段,就可以通过它的位置(offset)和大小(size)的实际数据进行搜索,逆向找到指向它的数据位置,并且继续逆向直到解析完整的文件。另外,如果要考虑设计的完备性,需要在二进制文件中加入一些冗余字段进行校验或者纠错,例如CheckSum、CRC32、Alder32、MD5、ECC等,这些通过hex编辑器很容易计算并发现。小程序中FileCount的字段,这完全是为了工程实践考虑的,在小程序中并没有出现这类的计算值,这是可能是因为小程序为了简单设计考虑,一旦发现包体被篡改或损坏就直接丢弃。
其实拆解小程序这种格式并不需要花费特别多的精力,因为其格式比较简单,而且从下图流程上来说,后缀为wx的二进制格式很可能与wxapkg格式是同源的。
开发者工具上传服务器分发原始代码后缀为wx代码包处理为wxapkg格式包体客户端
从开发者工具的代码中的pack.js很容易发现一些对wx格式封装的痕迹,只不过其中unpack.js的代码被隐去了。通过实际的分析发现(wxapkg文件可以通过截获网络包请求获得或者在本地的微信appbrand目录下可以发现),wxapkg格式就是将wx格式进行了转化:Wxml -> Html、 Wxml -> JS、Wxss -> Css,其二进制格式跟后缀名为wx二进制格式完全一致。我写了两个版本的解析二进制包的代码(Javascript版本传送门,python版本传送门),其实非常简单,根据小程序包结构一步一步解析,基本上没啥难度。但如果要将Html -> Wxml, JS -> Wxml, Css -> Wxss进行还原,其中JS -> Wxml的过程中需要将if语句转变成wx-if标签、for语句转化成wx-for标签有点麻烦,需要对解析包后的page-frame.html中 JS 代码进行修改,修改细节太多就不再详说了,总之微信小程序的代码没有经过额外的保护措施,比较容易进行还原。
(PS:暴露一下微信小程序未公开的API,openUrl- 在小程序中打开外部网页;getGroupInfo- 获得群的名称,群内成员昵称等数据;getOpenidToken - 获得用户openid;这些权限微信应该是没有准备开放的。每次在进入小程序时,客户端都需要先去请求该小程序的元数据,例如应用名、版本号、一些权限列表、代码包下载地址等描述信息,修改这些元数据可以获得相应的权限,小程序的关键信息完全由后台控制进行配置,另外小程序的本地文件存储采用HASH映射机制进行文件定位,文件存储在外部存储,本身通过自定义算法实现完整性校验 - 首先,小程序最终存储的文件名是:对称加密(文件流内容Alder32校验和 | 原始文件名)生成的,最终文件名和文件内容会通过自校验判断完整性;其次,本地缓存是通过HASH映射查找文件。所以即使能破解文件名和文件内容,绕过文件自身签名校验,篡改为攻击者的伪造文件,小程序APP也无法映射到该伪造文件进行使用。)
由上可见,微信并没有在代码安全上进行过多的考虑。这导致需要在应用审核过程中花费比较多的功夫,不然作品太容易被复制窜改,以至于会失去渠道先机,这对流量是致命打击。由于历史原因,前端的代码安全技术发展的比较缓慢,相比其他被编译成二进制的应用,前端这种纯文本应用,太容易被辨识与窜改。
对前端代码进行保护的目的在于让机器容易识别相关的指令,而使人难以理解代码的逻辑,但往往在对前端代码进行保护过程中,很难既兼顾指令效率又能使可读性降低。因此,常常需要在现有的代码中增加一些额外的验证逻辑,例如一些增加无效的代码进行混淆、采用守护代码保护业务代码不能在其他的域名下正常运行、增加一些防止调试跟踪的断点等,这些措施都是使得破解代码时人工成本增加,从而增加代码的安全性。
下面提供一些能够增加前端代码安全性的策略:
1. 精简(minify)
这是最简单且无害的方法,精简代码能减少代码体积,从而减小数据传输的负荷,同时也能降低代码的可读性。在小程序开发者工具中也提供该选项。对Javascript代码进行精简大致可以从以下几个方面入手:
删除注释,删除无意义或者多余的空白,删除可以省略的符号
删除一些没有调用的代码(Dead code),对函数进行精简(三元运算符?:、字符串操作、对象函数、对象继承、函数引用、无名函数、递归函数)
将变量名进行简化,将零散的变量声明合并,缩短语句
......
常用的工具有很多:YUI Compressor、UglifyJS、Google Closure Compiler、JS Packer、JS Min...
使用工具对代码进行精简时需要注意:1. 最好备份原始代码,方便调试与后期修改。 2. 用于调试精简代码时保存的sourcemap,在线上应该删除。 3.编写代码的时候应该严格按照规范,最好使用lint工具对代码进行检查,精简代码后导致代码不可用时,调试非常困难。
这种简单的方法虽然很实用,但是也很容易被还原出源代码,使用一些代码格式化工具可以补齐被删除的空格、换行、符号等,例如jsbeautifier。另外2015年就有相关的研究,从大量的代码中推测出被精简的代码,因为人写代码总有固定的范式,所写的代码相似性都非常的高,如果用统计方式就很容易反推源代码,苏黎世联邦理工大学Martin Vechev教授领带下开发的工具JSNice就是一款运用条件随机场(Conditional Random Fields)机器学习和程序分析方法来还原Javascript代码利器,利用大量的开源代码,去学习命名和类型的规律。JSNice可以用于以下不同的方面:反精简的JavaScript代码、对当前的代码提供更多的更有意义的变量名、自动化程序的注释等。相关论文传送门 后台代码传送门
2. 混淆(obfuscation)
混淆可以减低代码的可读性,防止被轻易追踪出程序逻辑。常见的混淆方法有如下几种:
通过编码混淆代码,这篇文章《Javascript常用混淆方法》里面介绍了很多不错的编码加密方法。但是这些方法有个明显缺点,增加代码体积,而且编码加密都是可逆的。
将标识符混淆和控制逻辑混淆(分离静态资源、打乱控制流、增加无义的代码等),例如aaencode和jjencode。
标识符混淆的方法有多种,有些与编码混淆代码方法有些重叠,常用方法有哈希函数命名、标识符交换和重载归纳等。哈希函数命名是简单地将原来标识符的字符串替换成该字符串的哈希值,这样标识符的字符串就与软件代码不相关了;标识符交换是指先收集软件代码中所有的标识符字符串,然后再随机地分配给不同的标识符,该方法不易被攻击者察觉;重载归纳是指利用高级编程语言命名规则中的一些特点,例如在不同的命名空间中变量名可以相同,使代码中不同的标识符尽量使用相同的字符串,增加攻击者对软件源代码的理解难度。
控制混淆是改变程序的执行流程,从而打断逆向分析人员的跟踪思路,达到保护软件的目的。一般采用的技术有插入指令、伪装条件语句、断点等。伪装条件语句是当程序顺序执行从A到B,混淆后在A和B之间加入条件判断,使A执行完后输出TRUE或FALSE,但不论怎么输出,B一定会执行。控制混淆采用比较多的还有模糊谓词、内嵌外联、打破顺序等方法。模糊谓词是利用消息不对称的原理,在加入模糊谓词时其值对混淆者是已知的,而对反混淆者却很难推知。所以加入后将干扰反汇编者对值的分析。模糊谓词的使用一般是插入一些死的或不相关的代码(bogus code),或者是插入在循环或分支语句中,打断程序执行流程。内嵌(in-line)是将一小段程序嵌入到被调用的每一个程序点,外联(out-line)是将没有任何逻辑联系的一段代码抽象成一段可被多次调用的程序。打破顺序是指打破程序的局部相关性。由于程序员往往倾向于把相关代码放在一起,通过打破顺序改变程序空间结构,将加大破解者的思维跳跃[3]。
另外还有些混淆方式是专门针对于反混淆工具设计的,这就需要去仔细分析反混淆工具的原理,在一些特定的地方插入代码使反混淆器进入死循环或者异常跳出。
一般来说,提供代码精简的工具都会提供一些混淆的方法,除此之外,比较知名的商业工具有jasob、jscrambler,一般越商业的越难被反混淆,然而这些高级的代码混淆也常会被用于隐藏应用中的恶意代码。对恶意代码进行混淆是为了躲避杀毒软件的检测,这些代码在被混淆扩充后会难以被识别为恶意软件。相应的也有一些反混淆的工具出现,例如上面提到的JSNice工具能够对混淆的代码进行推理,另外反混淆工具JSDetox专门针对一些混淆方法做过专门的支持。反混淆一直是一项体力活,根据不同的混淆策略需要进行反推演算,这就是一场攻与防的游戏罢了。
3. 加密(encryption)
加密的关键思想在于将需要执行的代码进行一次编码,在执行的时候还原出浏览器可执行的合法的脚本,在某个角度也可以认为是一种混淆的形式,看上去和可执行文件的加壳有点类似。Javascript提供了将字符串当做代码执行(evaluate)的能力,可以通过 Function constructor、eval、setTimeout、setInterval、Worker、DOM event等将字符串传递给JS引擎进行解析执行,由于有些需要用到eval函数,会导致代码性能会减低。以Worker执行举例:
var URL=window.URL || window.webkitURL;var Blob=window.Blob || window.webkitBlob;var blobURL=URL.createObjectURL( new Blob(['console.log("Hello World!")'], {type: 'application/javascript'}));new Worker(blobURL);URL.revokeObjectURL(blobURL);
有以下常见的几种加密方法:
base64编码,一种简单的方法就是将代码转化成base64编码,然后通过atob以及eval进行解码然后运行,另外一种采用base62编码技术更为常见,其最明显的特征是生成的代码以eval(function(p,a,c,k,e,r))开头。无论代码如何进行变形,其最终都要调用一次eval等函数。解密的方法不需要对其算法做任何分析,只需要简单地找到这个最终的调用,改为console.log或者其他方式,将程序解码后的结果按照字符串输出即可。
(PS: 从算法上看,packer是一种base64编码字典压缩策略,packer的base64编码的压缩率很高,精简后代码依然可以减少50%体积以上,因为带有解压器和字符表,越长的代码理论上压缩率更高,想要了解详情可以看看这篇文章《Packer,你对我的JS做了什么!》)
使用复杂化表达式,在Javascript中可以把原本简单的字面量(Literal)、成员访问(MemberExpression)、函数调用(CallExpression)等代码片段变得难以阅读。例如这个方法仅用+!等符号就足以实现几乎任意Javascript代码。在 JS 代码中可以找到许多这样互逆的运算,通过使用随机生成的方式将其组合使用,可以把简单的表达式无限复杂化。
隐写术,将 JS 代码隐藏到了特定的介质当中。如通过最低有效位(LSB)算法嵌入到图片的 RGB 通道、隐藏在图片 EXIF 元数据、隐藏在 HTML 空白字符、放到css文件中(利用content样式能存放字符串的特性)等。比如一张图片黑掉你:在图片中嵌入恶意程序,这个正是使用了最低有效位平面算法,结合HTML5的canvas或者处理二进制数据的TypeArray,抽取出载体中隐藏的数据(如代码)。隐写的方式同样需要解码程序和动态执行,所以破解的方式和前者相同,在浏览器上下文中劫持替换关键函数调用的行为,改为文本输出即可得到载体中隐藏的代码[4]。
混合加密,单个方法容易被破解,但组合起来就不会那么容易了,破解成本也会指数增长,例如jdists采用组合加密和嵌套加密的方式。
这些加密的方式都很容易通过对源代码进行词法分析、语法分析进行还原,首先将代码的字符串转换为抽象语法树(Abstract Syntax Tree, AST)的数据形式,然后从语法树的根节点开始,使用深度优先遍历整棵树的所有节点,根据节点上分析出来的指令逐个执行,直到脚本结束返回结果。这种方法大多数用于对代码进行优化,例如最近Facebook开源了代码优化工具Prepack,可以自动消除冗余代码,降低打包体积和执行时间,基本上就可以用来将这些加密的字符串进行还原,毕竟编码这些字符串都是可以通过词法语法推测出来的。
4. 编译(compile)
Github上有一份清单记录了所有Javascript扩展语言,这些语言都可以通过编译器转化为Javascript语言,这也是前端发展的一个趋势,原来写的html,css,Javascript已经开始变成了一个“中间语言”,而且越来越多的团队也有了自己的一套前端编译系统。Javascript越来越像Web中的汇编语言,特别是近些年Node的普及,让前端变得越来越复杂,大量前端框架的出现,使得Javascript代码可以通过手工编写,也可以从另一种语言编译而来,详情参考几年前Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),还有Mike Shaver(Mozilla技术副总裁)的邮件。通过编译后的Javascript代码越方便机器的理解,降低可读性,在某一定角度上讲,这也不愧为一种代码保护措施。据说几大科技巨头正在酝酿给浏览器应用设计一款通用的字节码标准——WebAssembly,一旦这个设想得以实现,代码保护将可以引入真正意义上的“加壳”或者虚拟机保护,对抗技术又将提升到一个新的台阶。目前在桌面端,使用NW.js框架可以JavaScript应用程序的源代码可以被编译为本地代码,在运行时通过NW.js动态还原出源代码,但是这种方法目前会比正常的JS代码慢30%左右。
5. 防止被调试
对代码进行破解分析无非分为静态分析和动态分析,如果对代码进行混淆加密等形式操作,那么静态分析就很麻烦了,对代码调试跟踪分析可以对代码整体逻辑有一个宏观的把控。例如首先判断浏览器是否开启了开发者工具控制台(目前最完美的解决方案传送门),如果检测出控制台开启则堵塞Javascript执行或让代码异常跳出。另外Android 4.4及以上和iOS是支持webkit remote debug的,因此应该在debug模式下,设置代码可以被debug,release模式下,禁止debug以保护数据安全。
6. 前后端协作
首先得强调的事情是不要在前端放敏感数据,前端容易破解,因此需要配合后端进行安全防护,例如微信小程序的登录,必须利用授信的后端配合才能完成此项功能,另外在小程序的网络请求中的referer是不可以设置的,格式固定为https://servicewechat.com/{appid}/{version}/page-frame.html,其中{appid}为小程序的appid,通过验证appid字段可以抵御一些直接的山寨,其次就是加快迭代速度更改代码保护策略,这样可以让之前的分析失效,增加破解的成本。
以上就是对当前前端代码安全进行的探索,最后用一句话结束:
Beneath this mask, there is more than flesh. Beneath this mask, there is an idea. And ideas are bulletproof.
作者:不详
出处:知识商店
运行环境不同
网页 —— 浏览器(内核渲染)
小程序 —— 微信环境API不同
由于运行环境不同,小程序无法调用DOM和BOM的API
但是小程序可以调用微信客户端的API,如定位,扫码支付等开发模式不同
网页开发模式: 浏览器 + 代码编辑器 (用记事本都可以敲出一个静态页面)
小城开发流程:1. 注册开发账号 2. 安装小程序开发工具 3. 创建与配置小程序
相比较之下,小程序上手比较麻烦。
使用浏览器打开 https://mp.weixin.qq.com/ 网址,点击右上角的“立即注册”即可进入到小程序开发账号进行注册。
点击注册小程序 -> 填写账号信息 -> 填写账号信息 ->点击链接激活账号 ->选择主体类型(这里选择为个人即可) -> 主体信息登记 - > 重点: 获取小程序自己的AppID,注册后在开发设置即可找到
小程序开发工具 是微信所推荐的开发工具
她所提供有主要功能
快速创建小程序项目
对小程序项目代码进行编写
进行编译和预览
上传代码发布
推荐下载和安装最新的稳定版(Stable Build)的微信开发者工具,下载页面的链接如下:
https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
效果:
pages 存放页面的文件
utils 存放工具性质的模块(脚本文件,如wxs脚本过滤文件)
app.js 小程序的入口文件
app.json 小程序的全局配置(配置窗口样式版本,窗口路径,tabBar导航条等)
app.wxss 小程序的全局样式
project.config.json 小程序的项目配置
sitemap.json 设置小程序可否被搜索到
每一个页面都有四个文件
.js 脚本文件(生命周期函数,存放数据,自定义函数)
.wxml 页面结构文件 (编辑页面UI结构)
.wxss 页面样式文件 (美化页面样式)
.json 页面配置文件 (配置页面,如当前窗口的外观,引用自定义组件)
json 是一种数据格式,在实际开发中,json文件总是以配置文件存在,在小程序中也不例外
app.json 是关于项目的配置文件可以配置:
window:全局定义小程序所有窗口样式(导航条,背景色,文字样式等)
page:页面路径配置(创建页面)
style:全局定义样式版本设置
sitemaplocation: 用来指明 sitemap.json 的位置
只需要在 app.json -> pages 中新增页面的存放路径,小程序开发者工具即可帮我们自动创建对应的页面文件,(放在第一位置路径的页面即为小程序主页面 这里为 index页面)
如图所示:
这个是整个项目的配置文件,用来配置记录我们对项目开发的个性化设置,如
setting:编译配置(如:设置是否检查sitemap)
appid:自己的appID
projectname: 项目名称
微信现已开放小程序内搜索,效果类似于 PC 网页的 SEO。
sitemap.json 文件用来配置小程序页面是否允许能被搜索到
当开发者允许索引时,微信会以爬虫的形式,为小程序的内容和项目名称作为索引,用户通过搜索关键字即可查到对应小程序
"action":"allow"
页面默认是被允许索引的,要取消索引只需要设置为disallow
页面的配置文件(局部配置)
可以用来配置当前页面的窗口样式,组件引用,与app.json的类似,
在page.json的配置项会覆盖全局样式app.json的配置项
WXML(WeiXin Markup Language)是小程序框架设计的一套标签语言,用来构建小程序页面的结构,其作用类似于网页开发中的 HTML。
WXML 和 HTML 的区别
① 标签名称不同
HTML (div, span, img, a)WXML(view, text, image, navigator)
② 属性节点不同
< a href=“#”>超链接< navigator url=“/pages/home/home”>
③ 提供了类似于 Vue 中的模板语法
数据绑定列表渲染条件渲染
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式,类似于网页开发中的 CSS(cascading style sheet)。
WXSS 和 CSS 的区别
① 新增了rpx单位
CSS 中需要手动进行像素单位换算,例如 remWXSS 在底层支持新的尺寸单位 rpx,在不同大小的屏幕上小程序会自动进行换算
② 提供了全局的样式和局部样式
项目根目录中的 app.wxss 会作用于所有小程序页面局部页面的 .wxss 样式仅对当前页面生效
③ WXSS 仅支持部分 CSS 选择器
.class 和 #idelement并集选择器、后代选择器::after 和 ::before 等伪类选择器
在小程序中,我们通过 .js 文件来处理用户的操作。例如:响应用户的
点击、获取用户的位置等等
小程序中的 JS 文件分为三大类(其他:自定义组件componnet),分别是:
① app.js
是整个小程序项目的入口文件,通过调用 App() 函数来启动整个小程序
② 页面的 .js 文件
是页面的入口文件,通过调用 Page() 函数来创建并运行页面
③ 普通的 .js 文件
是普通的功能模块文件,用来封装公共的函数或属性供页面使用
js(java script) 是一个实现业务逻辑的文件。
例如:Andriod安卓系统 和 IOS苹果系统,是两个不同的宿主环境,
安卓的应用必须要在安卓系统才能运行,这也是为什么 之前有些软件 安卓和苹果不能兼容了。
而小程序的宿主环境则是微信,小程序只能在微信上运行,小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能,例如:微信扫码、微信支付、微信登录、地理定位、etc
小程序的宿主环境微信所包含的内容
通信模式运行机制组件API
小程序中通信的主体是渲染层和逻辑层,其中:
① WXML 模板和 WXSS 样式工作在渲染层
② JS 脚本工作在逻辑层
小程序中的通信模型分为两部分:
① 渲染层和逻辑层之间的通信
由微信客户端进行转发
② 逻辑层和第三方服务器之间的通信
由微信客户端进行转发
微信客户端将代码包下载到本地
解析app.json 全局配置文件
执行小程序入口文件app.js,即调用app.js 的App实例(相当于一个类)
渲染小程序首页
解析page.json局部配置文件
执行页面入口文件page.js,即调用page.js 的page()创建页面实例
加载.wxml和.wxss 结构和样式文件
恭喜你!!成功扬起小程序的扬帆!!!
信小程序初步入坑小指南
https://developers.weixin.qq.com/miniprogram/dev/devtools/beta.html
打开链接下载小程序云开发
为json格式的文件,为一个配置文件,属于全局的
初始化的文件内容
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
pages
各个参数的解释,pages为页面,每一次更改页面,增加或者新增加页面都需要修改pages参数。不需要加后缀名,微信框架会自动添加后缀名。
window
对于全局导航栏的设置。
navigationBarBackgroundColor 设置全局的导航栏的颜色
navigationBarTitleText 设置导航栏的文字内容
navigationStyle 设置导航栏的样式
backgroundColor 设置窗体的颜色,即下拉刷新透露出的颜色
即需要设置 "enablePullDownRefresh": true, 其布尔值为true即可进行漏出设置的窗体颜色。
backgroundTextStyle 设置下拉的loding样式
tabBar
是下方的导航栏的设置。这个直接看文档吧。。
https://developers.weixin.qq.com/miniprogram/dev/framework/config.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE
app.json文件如下
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#c7dbc8",
"navigationBarTitleText": "小小",
"navigationBarTextStyle":"whiter",
"enablePullDownRefresh": true,
"backgroundColor": "#fceaea"
},
"tabBar": {
"list": [{
"text": "ming",
"pagePath": "pages/logs/logs"
}, {
"text":"home",
"pagePath": "pages/index/index"
}],
"color": "#f8fcea",
"backgroundColor": "#ff9999",
"selectedColor": "#c5ff99",
"borderStyle": "white",
"position": "top"
},
"functionalPages": true
}
该文件为一个本地的配置文件
就是对于微信的一些设置
wxml ≈ html
感觉很像ejs,好吧
wxml中内容
<text>{{mesg}}</text>
在同路下的js文件中
Page({
data: {
msg: "hello world"
}
})
渲染出来的结果
Page({
data: {
msg: "hello world"
},
clickMe: function() {
this.setData({msg: "3333"});
}
})
wxml
<text>{{msg}}</text>
<button bindtap="clickMe">点击按钮</button>
点击按钮将会自动更新页面的数据
客户端打开小程序之前,会把小程序全部下载到本地。
通过app.json可以知道页面的启动地址。
小程序只有一个app的实例,全局共享。
启动完成后触发onLaunch事件,然后运行回调函数
在小程序启动完毕以后控制台输出内容
App({
onLaunch: ()=> {
console.log('小程序启动完毕')
}
})
上方进行了一次回调
一个页面有四个文件,分别是json(配置文件),wxml(页面文件,类似于html),js文件(处理页面的相关交互,和网页类似)
js中有一个page,为一个页面的构造器,渲染页面的时候先装载json文件,配置当前的顶部导航,接着装载wxml文件,配置页面的DOM,在装载wxss,进行对页面样式的处理
和网页的类似,都是同样的
最后将会读取js文件,根据页面中的page函数即构造器中的内容,将wxml和data进行绑定,渲染出结果,为mvvm
mvc 分别是模型层,视图层,和控制器,当用户请求到达以后,将会先经过路由,即入口文件,即主文件中的server.js文件,接着进入lib目录下的route.js文件,对路由进行分发,路由在将数据传递给控制器,controller ,controller 收到请求以后再向model 索要数据,索要完成以后,在将数据导向view层,即ejs文件的地方,渲染完成文件以后返回给用户。 mvp 在mvc的基础上,view中不写逻辑,,在原先控制器的地方完成页面的合并
mvvm 和mvp类似,只不过view和原先的控制器双向绑定,即使用get 和 set方式,达到当数据更改的时候,进行回调
是滴,小程序采用组件化,例如生成地图map即可
ps 在网页中,生成地图,需要引入第三方的js文件,以及第三方的api,达到生成地图的目的。
api的回调为异步操作,所以呢,依旧要进行回调
发布者-订阅模型
小程序使用的是js引擎进行渲染,逻辑层将数据发送给视图层,视图层接受事件的反馈,开发者写的所有文件都会打包成为一份js文件,小程序运行时启动,小程序离开时销毁,
吐槽 一些浏览器里的js在微信小程序无法使用,小程序还有npm? 天哪,
注册程序
app()函数,必须在app.js文件中调用,接受一个object的参数
前台后台定义,当用户点击左上角关闭的时候, 或者按住home离开微信,小程序,没有销毁,将会进入后台,再次打开进入前台,当小程序进入后台一段时间以后,系统资源占用过高将会不定时的销毁
onLaunch
代码如下
//app.js
App({
onLaunch: (onlaunch)=> {
console.log(onlaunch)
}
})
效果如下
即获取页面的参数
相比较网页好轻松,,网页还需要进行先字符串分隔,然后再次以=进行分隔,然后将其循环,遍历该数组,将其保存进入对象,完成。如果使用json字符串进行传,可能稍微方便一点
getAPP
getApp函数能获取小程序的各种函数,即onLaunch等其他的一些函数
即获取到小程序的一个实例
注册页面
page为一个构造函数,接受对象,用来对页面进行初始化
data
data和渲染层,进行数据的绑定
onLoad
进行参数的传值
Page({
data: {
msg: "hello world"
},
clickMe: function() {
this.setData({msg: "3333"});
},
onLoad: (query)=> {
console.log(query);
}
})
onShow
页面切入的时候,将会进行显示。
例: 按住home按键,在回到小程序界面的时候,将会回调该注册的函数
onReady
页面渲染完成以后,将会回调该函数
onHide
页面切换的时候,将会回调注册的函数
onUnload
页面卸载的时候,将会触发
页面事件
onPullDownRefresh
用户下拉,需要设置onReachBottomDistance,即用户下拉到页面底部多少的时候,触发该事件
onPageScroll
用户滑动的距离,没有正负之分
onShareAppMessage
用户转发的接口
在button组件中设置
open-type="share"
即可设置为转发按钮
需要有return进行返回参数
onTabItemTap
单击tab将会触发该内容
onTabItemTap: (item)=>{
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
}
如果按住导航,将会进行输出
当单击组件的时候,发生事件
这一点终于和网页类似了。网页中也可以实现一个元素和事件进行相互的绑定
viewTap: ()=> {
console.log('您已经单击按钮')
}
<button bindtap="viewTap">这是按钮</button>
Page.route
当前页面的路径,类似于网页的
window.location.href
可以获取到当前页面的url
onShow: function() {
console.log('页面进行显示切入前台');
console.log(this.route);
},
当用户切换tab的时候,将会立马输出当前页面的path值
其中this指代当前的page,因为是在一个page函数内部
Page.prototype.setData
为page的继承函数,将数据从逻辑层发送到视图层(异步),this.data的值,(同步
)
ps 单纯的改变this.data的值,不会起作用,因为页面已经渲染完成,需要进行发送到视图层,进行更新视图
ps 是的。小程序使用的就是mvvm
js
Page({
data: {
text0: "11111",
text1: "22222",
text2: "333333",
text3: "444444"
},
changeText0: function() {
this.setData({text0: "22222"})
},
changeText1: function () {
this.setData({ text1: "33333" })
},
changeText2: function () {
this.setData({ text2: "44444" })
},
changeText3: function () {
this.setData({ text3: "55555" })
},
})
wxml
<view>{{text0}}</view>
<button bindtap="changeText0">更改上方文字</button>
<view>{{text1}}</view>
<button bindtap="changeText1">更改上方文字</button>
<view>{{text2}}</view>
<button bindtap="changeText2">更改上方文字</button>
<view>{{text3}}</view>
<button bindtap="changeText3">更改上方文字</button>
路由
小程序中的路由是有框架达到的
框架用栈的方式维护了当前的所有页面
ps 又见到栈了
getCurrentPages
该函数用于获取当前页面的栈,返回的是一个数组
适用于获取上一个返回的页面
全局变量
js文件中声明的变量,和函数只在文件中有用,不同文件可以声明相同的
ps 如果加载到一个页面的时候,将会发生命名冲突
可以在app.js文件中设置全局的数据
// a.js
var app=getApp();
console.log(app.globalData)
//app.js
App({
onLaunch: (onlaunch)=> {
console.log(onlaunch)
},
globalData: 333
})
模块化
类似于node.js的包
使用的同样是exports进行接口的暴露
是滴,类比node.js即可,是滴,小程序还不支持引入npm包
然后就可以欢快的引入npm包了。
ps 貌似有个小坑。
*请认真填写需求信息,我们会在24小时内与您取得联系。