微信小程序中的 JavaScript
微信小程序的业务逻辑都是通过JavaScript语言来实现的,本章我们将详细的讲解JavaScript的基本概念,以及在小程序中如何使用JavaScript语言。JavaScript是一种轻量的、解释型的、面向对象的头等函数语言,是一种动态的基于原型和多范式的脚本语言,支持面向对象、命令式和函数式的编程风格。
1 小程序的运行环境
1.1 MINA 框架介绍
小程序的开发框架被称为MINA框架,其框架结构如图1所示。
图1 MINA框架结构
通过上面的框架结构图,我们可以看到小程序的MINA框架有三个部分组成,首先是View视图层,其次是App Service逻辑层和Native系统层。小程序中所有的页面都在View视图层中,每个页面由WXML文件和WXSS文件来搭建页面视图的结构和展现样式。
App Service逻辑层是由App Service线程来加载、运行的,其生命周期常驻内存。App Service逻辑层顾名思义就是用来处理业务逻辑的,是MINA框架的数据交互服务中心。逻辑层有两个部分组成,一个是Manager,其主要功能是负责小程序逻辑处理部分的执行;另一个部分是底层提供的WAService.js文件,用于封装小程序的所有API接口,让其他平台的运行环境能够通过封装的API来使用微信客户端的能力。
小程序的MINA框架第三部分是Native系统层,这一层中接入了微信客户端的原生能力。小程序的视图层和逻辑层是双向通信的,在视图层和逻辑层之间提供了数据传输和事件系统。在视图层和逻辑层通过系统层的JSBridge进行通信,逻辑层把数据变化通知到视图层,然后触发视图层的页面更新,然后视图层再把事件通知给逻辑层进行业务处理。
那么在小程序的视图层中,是如何把数据变化实时地展示出来的呢?
首先,WXML其实就是一个具有元素、属性和文本的节点树结构,在节点树结构中,每一个节点都有一个上下文的关系,所以在渲染WXML的时候,小程序的运行环境会把WXML节点树转换成一个JavaScript对象。当逻辑层发生数据变更时,就需要通过App Service逻辑层提供的setData()
方法把数据从逻辑层传递到视图层。微信客户端的WebView容器在渲染节点内容时,会把传递的数据进行前后的差异对比,然后再通过diff算法进行计算,将计算后的结果应用在原来的节点树上,最后渲染出正确的UI界面。
学习小程序的MINA框架的底层实现原理可以帮助我们更加清晰的了解和认识小程序的开发,小程序在MINA框架上做了很多的优化,例如当逻辑层的App Service线程遇到阻塞时,UI线程照样可以正常地处理和渲染视图,这样也就避免了跨线程通信时的内存消耗。其实,小程序对MINA框架优化的地方还有很多,例如在小程序启动时也做了一些优化处理,这就需要我们继续学习小程序的启动和运行机制。
1.2 小程序启动机制
大家在平常使用小程序的过程中肯定会到这种情况,就是在小程序首次打开并启动的情况下,启动过程较长,如果后续再次打开的话,启动的速度就会很快。那么小程序是如何启动的呢?
其实,小程序有两种启动状态,一种是热启动,另一种是冷启动。
首先,我们先来看一下什么是热启动。当用户已经打开过某个小程序后,在一定时间内再次打开该小程序,就不需要再次重新启动了,只需要把后台态的小程序切换到前台使用即可,这个启动过程就是热启动。
小程序的冷启动是指用户首次打开的小程序被微信主动销毁后,再次打开该小程序就需要重新启动。小程序在什么情况下会被主动销毁呢?这里有两种情况,一种是小程序进入后台状态之后,客户端会帮助用户在一定时间内维持小程序的启动状态,当超过一定时间之后,微信客户端会主动销毁处于后台状态的小程序,这个超时时间默认为五分钟。另外一种情况,就是当小程序在短时间内连续收到两次以上的系统告警时,微信客户端也会主动销毁正处于后台状态的小程序,每次系统告警的间隔时间默认为五秒。
在小程序冷启动时,如果发现有新的版本,就会帮助用户异步下载最新版本的代码包,并同时使用微信客户端的本地代码包进行启动。小程序异步下载的最新版本的代码包需要下次重启启动小程序时才能被应用到小程序中,如果需要在本次下载最新版本代码包后就应用到小程序中,需要通过小程序提供的API来实现。
1.3 小程序加载机制
在了解过小程序的启动机制后,我们再来看一下小程序的启动流程,小程序的启动流程如图2所示。
图2 小程序的启动流程图
通过上面的小程序启动流程图我们可以看到,在图的左侧部分是小程序启动的时候,微信客户端里面的视图层和逻辑层的交互逻辑以及数据缓存的存取操作。在小程序启动时,会向CDN服务器请求最新版本的代码包。如果是第一次启动的话,用户要等到代码包下载完毕,并将最新代码注入到Web容器内执行之后才能看到小程序的页面。如果遇到网络不好的情况,用户就会感觉小程序启动的时间较长,微信客户端会吧代码包缓存到本地,在下次启动时,会从CDN服务器上请求是否有最新版本的代码包。
CDN其实就是一个内容分发网络,主要作用是帮助用户把请求的内容分发到距离用户最近的一个网络节点服务器,提高用户访问的响应速度和成功率,以此来解决网络带宽和服务器性能延迟的问题。
小程序在启动时会做一些校验,当有最新版本的代码包时,小程序会运行之前已经缓存好的代码包,同时异步下载最新版本的代码包,让用户在下次启动时来使用。
1.4 小程序对JavaScript的支持
微信小程序的主要开发语言就是JavaScript语言,开发者可以使用JavaScript语言来开发小程序的业务逻辑以及调用小程序的API来完成业务需求。
JavaScript是遵循ECMAScript标准,ECMAScript是一种由Ecma国际通过ECMA-262标准化的脚本程序设计语言, JavaScript是ECMAScript的一种实现。理解JavaScript是ECMAScript一种实现后,可以帮助开发者理解小程序中的JavaScript同浏览器中的JavaScript以及Node.js中的JavaScript是不相同的。
遵循ECMAScript标准的JavaScript语言由以下几个部分组成:
- 基础语法
- 数据类型
- 语句
- 关键字
- 操作符
- 对象
浏览器中的JavaScript构成如图3所示。
图3 浏览器中的JavaScript
浏览器中的JavaScript是由ECMAScript、DOM(全称 Document Object Model,即文档对象模型)、BOM(全称 Browser Object Model,即浏览器对象模型)三部分组成的,其中DOM和BOM对象模型为Web前端开发者提供了让操作浏览器的API,用于修改浏览器的表现,例如修改URL、修改页面展示、数据记录等。
Node.js是基于Google v8引擎实现的JavaScript运行时,它使用了高效、轻量的事件驱动以及非阻塞的IO模型。我们通常会将Node.js作为一门后端语言来使用。Node.js中的JavaScript构成如图4所示。
图4 Node.js中的JavaScript
Node.js中的JavaScript是由ECMAScript、NPM以及Native模块组成。其中,NPM是Node.js的包管理系统,通过NPM可以拓展各种包来快速实现一些功能,同时通过一些Native原生模块来实现Node.js语言本身不具有的能力,例如FS文件操作、HTTP请求等。
了解过浏览器的JavaScript和Node.js的JavaScript实现之后,我们再来看一下小程序的JavaScript实现。小程序的JavaScript构成如图5所示。
图5 小程序中的JavaScript
小程序的JavaScript是由ECMAScript、小程序框架、小程序封装的API模块组成的,与浏览器中的JavaScript相比,小程序的JavaScript没有BOM和DOM模型对象,所以类似于jQuery、Zepto这种浏览器类库是无法在小程序中运行的。而且,小程序中的JavaScript缺少Native原生模块和NPM包管理的机制,在小程序中是无法加载原生库以及无法直接使用NPM包的,如果想要使用NPM包,需要通过微信开发者工具提供的构建NPM功能来实现。
1.5 小程序宿主环境差异
小程序的JavaScript除了与浏览器的JavaScript以及Node.js的JavaScript实现有所不同之外,在小程序中,不同平台的JavaScript脚本运行环境也是有所不同的。小程序JavaScript脚本的运行环境主要包含以下三个平台的运行环境。
- iOS平台上,小程序的JavaScript代码运行在JavaScriptCore中,由WKWebView进行渲染;
- Android平台上小程序的JavaScript代码通过X5内核解析,然后由X5内核进行渲染;
- 在微信开发者工具中,小程序的JavaScript代码运行在nwjs中,由ChromeWeb进行渲染;
微信开发者工具中的nwjs是基于Chrome和Node.js运行的,又被称为Node Webkit,内部封装了webkit的内核,提供了桌面应用的运行环境,让浏览器中运行的网页程序也可以在桌面程序中运行。小程序宿主环境如图6所示。
图6 小程序宿主环境差异
在小程序的三个宿主环境中关于ECMAScript标准的实现是不一致的,ECMAScript标准截止到目前一共有八个版本,在Web前端开发中大部分环境使用的是ES5和ES6标准。但是目前在小程序中,iOS8、iOS9系统所使用的运行环境并没有完全兼容到ES6的标准,所以ES6的一部分语法和关键字在小程序中是不兼容的。这就导致在微信开发者工具中和真机中运行时,同样的代码所呈现的效果会出现不一致的情况。针对这种问题,在开发微信小程序时可以使用微信开发者工具上的远程调试功能,实时查看小程序在真机上的表现。
2 生命周期
2.1 应用的生命周期
小程序的生命周期分为小程序应用生命周期和小程序的页面生命周期,我们先来看一下小程序应用生命周期,如图7所示。
图7 小程序应用生命周期
小程序应用生命周期有四个钩子函数,分别是onLaunch、onShow、onHide、onError,具体代码实现如例1所示。
【例1】小程序应用生命周期
// app.js
App({onLaunch() {},onShow() {},onHide() {},onError() {}
})
当用户第一次进入小程序的时候,微信客户端会帮助用户初始化小程序的运行环境,同时会从CDN服务器下载或者是从本地缓存中获取小程序的代码包,然后把代码注入到运行环境中。小程序初始化完成后,微信客户端会向小程序逻辑层的app.js文件中的app实例派发onLaunch事件,此时就会调用app.js文件中的App构造器参数上定义的onLaunch()
钩子函数。
在进入小程序后,用户可以通过小程序界面右上角的关闭按钮或者是手机上的home键离开小程序,离开后并没有立即销毁小程序,而是进入后台状态,此时就会调用App构造器参数上定义的onHide()
钩子函数。当用户通过热启动再次回到小程序时,微信客户端会把后台状态的小程序唤醒,此时小程序进入前台状态,同时调用App构造器参数上的onShow()
钩子函数。当小程序发生脚本错误时,或者小程序API调用失败时,会触发App构造器参数上的onError()
钩子函数。
2.2 页面的生命周期
小程序页面生命周期钩子函数如图8所示。
图8 小程序页面生命周期
小程序页面生命周期有五个钩子函数,分别是onLoad、onShow、onReady、onHide以及onUnload。其具体代码实现如例2所示。
【例2】小程序页面生命周期
// page.js
Page({onLoad(options) {}, // 监听页面加载onReady() {}, // 监听页面初次渲染完成onShow() {}, // 监听页面显示onHide() {}, // 监听页面隐藏onUnload() {} // 监听页面卸载
})
当小程序页面加载时,微信客户端会向逻辑层定义的page实例派发一个onLoad事件,此时Page构造器参数上定义的onLoad()
钩子函数就会被调用,unLoad()
方法在页面被销毁之前只会调用一次,在该方法中可以获取到当前页面被调用时的一些打开参数。
小程序页面显示之后,Page构造器参数所定义的onShow()
构子函数就会被调用,onShow()
方法是在每次页面显示时都会被调用,页面初始化完成后也会被调用一次,当用户从别的页面返回当前页面时也会被调用。在当前页面初次渲染完成后,Page构造器参数上定义的onReady()
钩子函数就会被调用,onReady()
方法是在onShow()
方法之后被调用的,并且在当前页面被销毁之前只会调用一次。
onReady()
方法被触发之后,逻辑层就开始与视图层进行交互了,用户在当前页面基础上再次打开一个新页面时,当前页面会触发Page构造器参数上定义的onHide()
钩子函数,在关闭当前页面时,会触发Page构造器参数上的onUnload()
钩子函数。
小程序是由两大线程组成,分别是负责页面视图的View线程和处理数据与服务的App Service线程,两大线程如图9所示。
图9 View线程和App Service线程
两大线程协同工作来完成小程序页面生命周期的调用。当小程序首次启动后,两个线程会被同时创建,当App Service线程创建后会依次调用onLoad()
和onShow()
方法,开发者可以在这两个方法内发送HTTP请求。当View线程初始完毕之后,App Service线程也已经初始化完毕,此时也会触发页面的首次渲染。View线程渲染完页面后,会再次告诉App Service线程渲染结果,同时也会触发onReady()
钩子函数的调用。onReady()
钩子函数调用完毕后,如果之前发送的HTTP请求已经拿到服务器返回的数据,那App Service线程就会把服务返回的数据再次发送给View线程,View线程再次渲染视图,直到当前页面销毁并触发App Service线程的onUnload()
钩子函数。
3 模块化
在小程序中,可以将一些公共代码抽离成一个单独的JavaScript文件,一个JavaScript文件就是一个模块,一个JavaScript文件中也可以有多个模块。模块可以通过module.exports或者exports对外暴露接口。exports是module.exports的一个引用,如果在模块中随意更改exports的指向会造成未知的错误,所以推荐使用module.exports来暴露模块接口。
模块对外暴露接口的代码如例3所示。
【例3】导出模块
// common.js
function sayHello(name) {console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {console.log(`Goodbye ${name} !`)
}module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
在需要使用这些模块的文件中,使用require将公共代码引入,实现代码如例4所示。
【例4】导入模块
var common = require('common.js')
Page({helloMINA: function() {common.sayHello('MINA')},goodbyeMINA: function() {common.sayGoodbye('MINA')}
})
4 小程序的API
为方便开发,微信小程序封装了一些API模块,方便开发者快速实现一些功能,这些API模块包括调用移动设备的基础能力、访问移动设备的硬件能力以及微信的开放能力。
小程序API提供的开发能力包括网络访问、存储、路由、跳转、转发、界面交互、数据缓存、系统文件访问、位置等一系列模块。开发者可以借助这些API实现更多的需求开发,我们会在后面的章节中详细介绍小程序的核心API。
5 本章小结
本章主要介绍了微信小程序的JavaScript实现,以及微信小程序中的JavaScript与浏览器的JavaScript和Node.js中的JavaScript的区别。通过本章的学习,了解了微信小程序的启动和加载机制,掌握小程序应用生命周期和页面生命周期钩子函数,这些钩子函数在小程序项目的开发中应用非常广泛,需要初学者熟练掌握。