小程序上线至今已有1年多时间了,我跟大家一样,目睹了小程序生态的演进,和API的变化。时下,打开某度搜索小程序开发,可谓是海量结果,然而真正理解了小程序,有质量的开发教程可谓“凤毛麟角”。我在这个开篇里想跟大家先探讨一下小程序的运行原理,做到知己知彼,再对症下药,用比较正确的开发姿势和工具去开发小程序。
语言与渲染环境
小程序的开发语言是JavaScript(不知道的请举手),小程序早期Runtime并不支持ES5+的语法,现在你可以看到的较新版本的DevTools里有个“ES6转ES5”,这个内置的预处理极大的方便了编码,tips:小程序ES6代码转译也是用的Babel,对API的支持情况可以看看这篇文档https://developers.weixin.qq.com/miniprogram/dev/devtools/details.html#客户端es6-api-支持情况。
在这里照顾一下新同学,简单说说关于Javascript和ES的概念,老同学请略过这一段。
准确地说,ES(ECMAScript)并不是什么语言级别的东西,它的Level可比语言要高,一般称为“Specification(规范)”。JavaScript只是对这个规范的具体实现和扩展,玩过AdobeFlash的同学(暴露年龄~~),肯定知道ActionScript,它也是ECMAScript的实现。世上本没有ES5,只是因为有了ES6(版本号:ES-2015),然后是ES7(ES-2016)。
那么这些ESx有什么样差异呢?这得分阶段来看了:
在ES2015规范问世后相当长的一段时间内,大概是JavascriptCore/V8等这些解释器还没准备好拥抱它,在这段时间里,需要把新的语法转换成旧的写法,解释器才能正确的解释执行。因此就出现了Babel这样的转译器以及各种Polyfill模块;后来,Mozilla/Google/Apple这些大厂开始升级解释器,陆续内建地支持ES新语法。(不能再写这个了,不然我得把文章标题改为JavaScript的前世今生啦~~)
回到微信小程序里来,就语言环境来说,有三种:
- iOS:JavaScriptCore/JsCore
- Android:X5,基于webkit(WebCore + JsCore)
- 开发工具:nwjs(原名是node-webkit),基于 Chromium和 Node.js运行
总体上说来,这三个语言环境没什么太大的差异,无非是各自的各个版本对ES语法及新特性的支持情况有稍许不同。对开发来说,无需过多关心这个,打包上传时“ES6转ES5”就OK啦,有坑再填。
生命周期
小程序管理生命周期的对象有三个,App,Page和Component,其中Page和Component都属于UI层面。每个对象都有自己的生命周期,并且这些对象的生命周期函数在运行时也是穿插在一起的,因此不能孤立地看待不同对象的生命周期。
我们先来看看这三个对象的具体情况:
App生命周期
App只会初始化一次,也就是说 app.onLaunch
回调只会call一次,紧接着就call app.onShow
,进入本次启动后要显示的Page的生命周期。
当退出小程序时,call 当前页面的 page.onHide
,最后调用 app.onHide
,show和hide循环往复。
Page生命周期
当页面被请求时,页面初始化(包括内部引用组件的创建),然后调用 page.onLoad
,页面加载完毕后, page.onShow
。
当离开页面时,有两种情况:
如果页面出栈(如:导航的redirect/reLaunch),则调用
page.onUnload
如果页面不出栈(如:导航的navigate),则调用
page.onHide
Component生命周期
组件从创建到渲染前,依次调用 component.created
和 component.attached
回调,渲染树创建完成后,开始首次渲染,调用 component.ready
回调。
当组件节点在DOM Tree上移动后,调用 component.moved
;
当组件被卸载(通常是条件渲染或者Page被卸载)时,调用 component.detached
,组件的生命周期结束。
App与Page的生命周期关系
当App完成启动阶段后,开始进入本次启动的首个页面的生命周期,依次调用 page.onLoad
和 page.onShow
。
当App退出(切到后台)时,会先调用当前显示页面的 page.onHide
,然后调用 app.onHide
;
当App唤醒(切回前台)时,会依次调用 app.onShow
和 本次启动的首个页面的 page.onShow
;
Page与Component的生命周期关系
)
小程序组件在引用页面初始化时创建(未渲染),依次调用 created
和 attached
,当所有关联组件节点被加入DOM Tree时,引用页面上触发 page.onLoad
。同时,组件节点开始进行渲染树合并,主要是混入CSSTree。
当组件节点首次渲染完成后,组件内调用 ready
;
当全部关联组件 ready
后,引用页面上触发 page.onReady
;
视图/数据渲染
在讲渲染前,先来看一下大家耳熟能详的两种模式(早期靠手的时代这里就略过不说了):双向绑定和单向数据流。
双向绑定
通过代码逻辑,把Model和View binding在一起,更新Model时,View就会自动更新,反过来,如果更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。
典型框架:Vue。
单向数据流(单向绑定)
单向数据流模式主要是加入了一个Store做状态管理,View上触发动作,动作改变Store,Store变化引起View更新(单向数据绑定)。
典型框架:React。
我们这里重点不是去讨论单向和双向的问题,因此不做更多展开描述,主要来看看小程序的数据渲染方式。
微信小程序使用的是单向绑定,有经验的同学可能从 setData
这个函数就猜想出来了。一起来一段简单的例子:
// page.js
Page({
onLoad(){
wx.request({
url: 'https://api.server.com/data',
success: this.setData.bind(this)
})
}
})
<block wx:if="{{data.sample}}">
<view>{{data.sample}}</view>
</block>
<block wx:else>
LOADING...
</block>
上面这个简单的例子中,一开始page会显示“LOADING...”,当HTTP请求完成后,页面会显示API服务器响应的数据。
这就是小程序数据渲染的大致过程了。
setData之后?
setData之后,到view被更新之前,发生了什么样的细节呢?欢迎在留言区表达你的看法。
公众号 前栈笔记