序言
几个月前,十分有幸参加微信小程序第一批内测. 但那时,远没有现在激动. 因为,那个时候并不曾意识到几个月后, 微信小程序将刷爆你的朋友圈。 21日深夜,微信官方放了一份关于”小程序”的内测邀请函,某公众号高调推送一篇文章,迅速引起朋友圈刷屏,很快便突破了10W+访问. 甚至有人连夜通宵写”小程序”开发教程. 这几日,内测的几个团队纷纷给出开发体验,于是我也把平日同事比较好奇的几点问题以及自己的看法和心得总结出来.
开发体验过程中,感觉到微信小程序应该算作一种 Hybird App,但并非像phoneGap
一样视图完全依靠webview来显现,然后通过封装的 Javascript 与Native API通讯. 小程序的视图内, 即可以包括webview,也可以包括native view. 互相覆盖叠层展示. 相对来说,这种开发难度更大,但是实现起来比单纯的webview更加灵活. 同时对于H5中性能不足的地方,可以用Native实现. 对于用户体验来说是更接近原生了. 小程序的应用框架MINA完成了最难的部分,作为应用开发者的我们,只需要微信提供的wxml,wxss即可完成接近于H5的开发体验,完全不必去了解我所用的INPUT是 webview 的 INPUT
还是 Native 的 INPUT
。接下来就一步一步带你走进小程序的世界。
应用框架MINA
MINA 这个名字就由来就好比MariaDB名字的由来。:0)
这是开发给到的架构图.
从图中可以看出,应用层和页面视图层之间的通讯是经由系统层的JSBridge实现的,页面单向接受来自应用逻辑层的数据流,应用逻辑层响应页面视图层的事件。页面视图层的每一个页面由wxss和wxml构成。而系统层JSBridage提供原生各种能力支持.
官方提供了开发者工具, 虽然开发者工具和微信客户端实现的原理大不一样,但我们依然可以从这里去理解页面视图层和应用逻辑层.
开发者工具和我们平时Chrome调试一样,左边是页面,右边是控制台。但不同的是, 左边展示的页面和右边的代码似乎不是运行在同一个环境。因为平时调试时,我们在console输入document.body.outerHTML
会打出页面HTML代码。在开发者工具中只会打印出appservice的页面内容。并非显示页面的内容。
同时刷新左侧区域,右边代码不会再次执行,除非点重启按钮。因此怀疑左侧页面和右侧控制台并非同一环境, 可以认为左侧页面就是架构图中的页面视图层,右侧控制台(除了wxml Tab外) 就是逻辑应用层。逻辑应用层想要更新页面就必须使用:
this.setData({text: 'update text'});
除此之外,别无它法。妄想通过DOM操作、Zepto、或者其它框架来更新页面是行不通的,因为在应用逻辑层无法直接获得页面视图层中的DOM节点。甚至在手机客户端上,完全没有window、document等全局对象。
开发者工具只是对整个架构的模拟,其中应用逻辑层应该还是运行在webview之上的,手机客户端的实现原理应该大不一样、应用逻辑层有可能完全不运行在webview上,这也就是为何手机客户端没有window、document等对象。
数据绑定探究
小程序实现数据绑定的方式如下:
<view>{{msg}}</view>
视图中以wxml语法添加一个节点,对于Vue或者是AngularJS用户是不是感觉颇为亲切和熟悉。唯一的区别就是没有v-text
或者ng-bind
的功能。
Page({
data: {
msg: 'Orginal msg'
},
onLoad: function () { this.setData({
msg: 'Updated msg'
});
}
});
更新数据时,对比三者的代码实现:
// 微信小程序this.setData({
msg: 'Updated msg'});// Vuethis.msg = 'Updated msg';// AngularJS$scope.msg = 'Updated msg';
接着我们加入另外一个INPUT
<input value="{{msg}}"></input>
对于INPUT
,小程序并不存在类似 ng-model
或者是 v-model
的双向绑定指令,只能通过 value
进行设置.
当用户人工在INPUT
中修改其中的值后,发现 view 中的值并不会跟着变, 这里与 Vue
和 AngularJS
表现不一致.
因为小程序是应用逻辑层到页面视图层的单向绑定,所以在应用逻辑层中不会感知到值的变化,而且也并不提供一个 getData
的方法去取到INPUT
中的值,只能通过事件响应实现。而 Vue
和 AngularJS
的双向绑定特性,人为在页面上的数据改变是可以直接反馈到逻辑代码里的。如果一定要实现类似功能,那就只有通过响应INPUT
事件,实时更新view的数据.
我们再在 页面加个按钮:
<button bindtap="btnTap">Button</button>
逻辑层加入响应事件:
btnTap: function () { this.setData({
msg: 'Updated msg'
});
}
手动改变INPUT
值为其它时,INPUT
的值却并没有改回来。这里说明在 setData
操作时,应用逻辑层会先检测msg是否有变化,而此时应用逻辑层感知不到到人为修改的INPUT
值,因此 setData
操作会被视为没有改变数据而不会去更新视图。所以官方提供另外一种方法强制更新视图:
this.setData({
msg: 'Updated msg',
}, {forceUpdate: true});
至于为何在 setData
时要检测数据是否变化过呢,而不是每次 setData
都去直接更新视图呢?我猜想是在应用逻辑层数据传递到页面视图层的这个过程,并非像暜通H5中dom.innerHTML = 'updated';
一样简单,因此做这个检测也是起到对性能的一种保护作用吧。
再来看另外一种情况,非当前执行序列下更新数据。
修改btnTap
事件如下:
btnTap: function () { var self = this; this.setData({
msg: 'Updated by button tap'
});
setTimeout(function () {
self.setData({
msg: 'Updated by setTimeout'
});
}, 3000);
}
但是结果并非像预期的那样三秒后改变文字。同理,此类情况如果是用AngularJS
实现需要修改为:
setTimeout(function () {
$scope.msg = 'Update by setTimeout';
apply();
});
添加一行apply();
或者用提供的$timeout
方法。
而Vue即便在setTimeout
中也可以不作改变,直接赋值。因为它的数据绑定是基于getter
、setter
的。
所以在MINA
中也可通过类似方法实现:
setTimeout(function () {
self.setData({
msg: 'Updated by setTimeout'
});
self.update();
}, 3000);
所以看到这里,MINA
在做数据绑定的时候和AngularJS的脏数据检查机制很像呢。
请求后台接口
小程序提供了一系列网络请求API,支持 HTTP
请求、webSocket
请求、以及上传下载文件。
但前提条件是在后台配置好了合法域名,只有合法域名的请求才被允许。
因为业务只涉及到 HTTP
请求, 所以这里只讨论由wx.request
发起的 HTTP
请求。
官方规定最多只允许5个并发请求,并且暂时不提供定制的方法。
这个让我联想到了多数浏览器对单个域名最高并发数量设置为6个。与之不同的是:
浏览器的策略是针对单域名,但小程序中是针对所有请求(但因为合法域名只有一个,所以也就不存在多域名的问题)。
浏览器在单个域名请求超过6个时,只是会暂时阻塞后续请求,直至某个请求完成,但小程序似乎在多于5个请求并发时直接报错。
先忽略这个问题,那么,线上已经开发好了的接口是否可以直接使用呢?那就看以下几点:
小程序有新的AppId,如果以前接口是针对老的AppId开发的话,那肯定不适用。
自从 iOS9 推出 ATS 特性后,要求 App 内访问的网络必须使用
HTTPS
协议以保证网络链路安全,所以小程序也需要接口支持HTTPS
协议。客户端对于
HTTP
协议的一些特性不完全支持,比如cookie
。因此如果接口从cookie
读数据的,就需要修改为从参数读取。同理写cookie
也需要修改为返回在body
中,然后在逻辑层用storge
API模拟实现cookie
。另外还有一些比如返回Content-type
必须为utf-8
,否则客户端解析乱码等问题,都需要在接口改造时注意。出于安全考虑,部分
header
用户是无法自行定义的,如果接口中存在Referer
校验等类似问题的话可能要重新修改校验规则。请求由客户端发出,因此为方便跨域的
jsonp
请求就没有存在的必要。
综上所述,在请求后端接口上大体还是和以前体验差不多的。
客户端请求是由客户端发起的比较好理解,因为之前就判断客户端的应用逻辑层代码不是运行在webview上。小程序开发者工具的应用逻辑层代码应该是运行在webview上的,那么它的 http
请求是由 node
发起的还是 webview 发起的呢?
出于好奇研究了一下,发现 wx.request
在开发者工具中是由 webview 发起的 xhr
请求:
既然是 webview 发起的 xhr
那么肯定就会受到浏览器跨域安全策略的限制。
分别在普通浏览器和小程序开发者工具的 console
中注入 jQuery
后执行:
$.get('http://www.qq.com');
可以发现普通浏览器会在xhr.send()
时报错,但开发者工具不会,而且能正常返回结果。
普通浏览器:
小程序开发者工具:
由此可见,开发者工具使用了一些手段屏蔽或者绕过了 webview 的跨域安全策略。
发布更新策略
微信小程序致力打造一个类似 AppStore
的生态系统,甚至网上有人脑补了一下 iPhone 8
的桌面。当然,这个只是个玩笑。
微信小程序使用提供的开发者工具开发,开发完后提交审核,微信审核通过后还需要开发者手动点击发布,这样小程序才会发布到线上提供服务.
一个微信小程序包括的所有资源就是wxml页面、wxss样式、json配置文件、js应用逻辑代码、图片多媒体等资源。启动小程序时,将所有资源下载本地运行,启动一个小程序相当于开启一个 webview 这里与微信H5不一样的是,在一定时间内除非手动关闭,即使返回打开另外一个小程序,原来的小程序也是一直运行在内存里,方便第二次快速唤起不需要等待时间。就如同在不同的原生APP中切来切去一样。即便关闭小程序,重新打开时,也无需重新下载,而是直接从本地启动。
在提交审核时,开发者工具会给出超出大小限制
这里的意思是超出了64 kb,最大为1024kb, 1088kb - 1024kb = 64kb
许多H5都是远远超过这个数据,不知道未来是否会允许更大的小程序呢? 小程序大了之后发布新版本,客户端的版本更新策略是怎么样的呢?
微信小程序现在依然处于内测阶段,版本更新策略是否确定还是未知数,不过我个人认为不外乎以下几种策略:
后台静默更新:用户只需要开着微信,那么会在闲时自动检测哪些小程序有发布新版本,并静默更新。
对于未来,不知道可安装的小程序数量是否有上限,小程序过多时,此类更新自然是弊大于利,浪费用户过多流量。进入时等待更新: 用户进入某个小程序时,先检测是否有先版本更新,如果有,下载新版本,下载完成后进入新版本小程序。
版本更新时,会有一个下载等待时间,影响用户体验,但同时也是最近的影响版本更新的一种方式。进入时不等待后台更新:用户直接进入小程序,并检测更新,如果有更新,静默下载,用户重启后更新小程序版本。
优点是不影响用户打开速度,但缺点是最新的版本更新需要下一次进入时方能体验。进入时不等待,弹窗提示版本更新: 用户直接进入小程序,并检测更新。如果有更新,弹窗提示需要是否需要重启更新小程序”
优点是用户可以自行选择立刻更新还是下次更新。
当然,如果能将以上几个策略结合起来那自然是更好,比如:
定义更新类型,根据更新的类型来决定采取哪种更新方式,如定义更新类型有 功能性迭代
,紧急BUG修复
,那么紧急BUG修复
肯定就以 1
或者 2
的方式更新。功能性迭代
就可以以 3
和 4
的方式更新。
因为更新内容大部分为文本文件,甚至可以考虑使用元数据代替文件的方式进行更新,这样就能大大减少需要下载的文件内容,比如这次发布我只更新一行代码,那么客户端需要下载的就只有那一行代码,而非整个文件。
根据开发时的体验来看,猜测内测版本的更新策略应该是第2种,至于正式版本是否会继续延用现在的更新策略那就不得而知了。
后记
以上体验都是在手机充值小程序的开发过程中的心得与体会,希望能通过梳理的几点问题能够大致了解到小程序的工作方式。因为我们的业务比较简单,使用到的技术可能只是MINA中的冰山一角,所以本文涉及的内容有一定的局限性,后续新的心得与体会也会在这里补充,更新。
从8月初开发小程序到现在,起初每天工作超过15个小时,从刚开始一步一个坑,框架改了又改到现在框架基本稳定,开公内测。这一切都离不开WX GG们的辛苦努力,这帮天才GG们卖得一手好萌,写得一手好代码,更重要的是他们精力充沛,无时无刻都在帮我们定位问题,解决问题。付出的努力是值得的,从现在小程序的轰动趋势来看,小程序注定要创建一个大世界。
最后,手机充值小程序井然有序的在进行着一步一步的迭代,这个过程更加顺畅得心应手,感谢手机充值团队的产品、后台、视觉、重构等同学付出的努力,希望大家多多支持手机充值,也希望在小程序上线之日,手机充值小程序能以最优雅的姿态与大家见面。
@Gcaufy wuli 哥哥,厉害了。 哪里都是你.....