使用微信小程序开发已经很长时间了,对小程序开发已经至关熟练了;可是做为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感受仍是不够的,咱们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能会解释咱们在开发过程当中遇到的一些疑惑,好比为啥小程序不能操做dom、小程序是web技术渲染仍是native技术渲染等等,另外一方面对于咱们我的成长也是有帮组的。javascript
首先声明下,文章查看小程序开发者工具源码的方法仅限学习使用。html
本文将从如下几个方面来讲一下小程序的实现原理前端
下面咱们经过微信小程序开发者工具的源码来讲说小程序的底层实现原理。以开发者工具版本号State v1.02.1904090的源码来窥探小程序的实现思路。如何查看微信源码,对于mac用户而言,查看微信小程序开发者工具的包内容,而后进入Contents/Resources/app.nw/js/core/index.js,注释掉以下代码就能够查看开发者工具渲染后的代码。vue
// 打开 inspect 窗口 if (nw.App.argv.indexOf('inspect') !== -1) { tools.openInspectWin() }
而后重启小程序开发者工具,就出现以下左侧页面,点击其中一个页面就能看到view层的dom结构,以下图右侧。
|
java
小程序的架构设计与web技术仍是有必定的差异,其吸收了web技术的一些优点,同时也摒弃web技术中体验等很差的地方。下面经过问题的形式来讲说小程序架构中的一些设计点。android
开发太小程序的都知道,小程序是双线程设计,即视图渲染与业务逻辑分别在运行在不一样的线程中。这个设计主要是解决web技术中的一个痛点:ios
web页面开发渲染线程和脚本线程是互斥的,长时间的脚本运行可能会致使页面失去响应或者白屏,体验糟糕。git
小程序为了更好体验,将页面的渲染线程和脚本线程分开设计在不一样线程中执行,具体实现:web
这样解决了长时间的脚本阻塞页面渲染的状况,可是也带来一些新的问题:canvas
开发者工具使用webview加载业务逻辑层的代码,虽然依赖的环境有DOM和BOM api,为了保持一致;小程序对全部的模块进行了局部化处理使其不能访问这些api。这样双线程经过native,开发者工具经过后台websocket服务来进行两者消息中转。具体能够参考官网图:
页面渲染的方式主要有三种:
由于小程序的宿主环境是微信,不太可能使用纯native渲染,不然全部小程序须要跟微信一块儿编码发版。采用纯web渲染貌似是可行的,支持快速在线更新,经过加装最新资源到本地便可渲染;可是纯web渲染在一些有复杂交互的页面上可能会面临一些性能问题,这是由于在web技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易致使一些逻辑任务抢占UI渲染的资源。因此小程序采用Hybrid方式渲染,用官网的描述以下:
界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每一个小程序页面都是用不一样的WebView去渲染,这样能够提供更好的交互体验,更贴近原生体验,也避免了单个WebView的任务过于繁重。
既然采用Hybrid方式渲染,那么页面的渲染可能会用到原生native来渲染,什么状况会用到原生渲染呢?
答案是使用到小程序提供的map、video、canvas、textarea等组件,页面中原生渲染的渲染原理能够参考官网原生组件。可是在小程序开发者工具中原生组件是使用html标签来模拟实现的。具体能够看下一节的map组件渲染结果。
上面说到小程序主要由成熟的web技术渲染,可否直接使用html提供的标签如div、table等组织页面呢,答案不能够。主要考量:
因此,小程序不能直接使用html标签渲染页面,其提供了10多个内置组件来收敛web标签,而且提供一个JavaScript沙箱环境来避免js访问任何浏览器api。
既然小程序不能直接使用html标签来渲染页面,那它提供的如view、cover-view等内置组件是否意味着最终都转换为html提供的内置标签来渲染呢?答案当不是。咱们来看以下代码:
<view class="map-container"> <map latitude='39.9088230000' style="height: 100%; width:100%;" longitude='116.3974700000' scale='16' id="id" bindregionchange="onRegionChange"></map> <view catchtap="onTap">test</view> </view>
上面代码在开发者工具中最终渲染元素以下图:
能够看出,小程序提供的组件并无最终转换为为html对应的标签来渲染,而是使用自定义的元素来渲染。这些内置组件都是由Exparser框架负责管理,它内置在小程序基础库中,为小程序的各类组件提供基础的支持。
Exparser框架基于Shadow DOM模型,模型上与WebComponents的ShadowDOM高度类似,具体能够参考官网组件系统。
内置组件的命名规范都是以wx-
开头的,外部引用内置组件如view,最终会调用底层的wx-view
组件;Exparser的view组件建立方式以下:
小程序为了管控与安全,提供一个JavaScript沙箱环境来运行JavaScript代码,js代码不能访问任何浏览器相关的接口,那就意味着js是不能操做dom和bom的,不然可能报错。小程序实现沙箱环境呢?即经过将业务逻辑封装到一个局部环境中,局部环境修改dom和bom的相关api指向。具体封装形式以下:
define("pages/xx/xxx.js", function(require, module, exports, window,document,frames,self,location,navigator,localStorage,history,Caches,screen,alert,confirm,prompt,fetch,XMLHttpRequest,WebSocket,webkit,WeixinJSCore,Reporter,print,URL,DOMParser,upload,preview,build,showDecryptedInfo,syncMessage,checkProxy,showSystemInfo,openVendor,openToolsLog,showRequestInfo,help,showDebugInfoTable,closeDebug,showDebugInfo,__global,WeixinJSBridge){ 'use strict'; // your code here })
那么问题来了,小程序是怎么给业务代码加上以上封装的呢?其实很简单,在小程序开发者工具中有一个后台服务,访问小程序的每一个模块的path时,后台服务会调用wrapSourceCodeInDefine方法将请求的JS文件的内容分别包裹在define域中,方法的代码以下图所示:
这里的define是小程序底层实现模块化的方法之一,还有一个是require方法;经过define来定义一个模块,require来引用一个define定义的模块。从上面小程序对业务模块代码的封装能够看出:
能够看看require定义的源码:
在实际的微信环境,业务逻辑层运行在JSCore中,其没有浏览器相关的信息,访问dom无从谈起;可是小程序开发者工具使用webview来运行业务逻辑代码,它有dom相关接口;因此经过上面沙箱环境来统一使js没法操做dom。
业务代码没法访问dom,怎么实现页面动态更新呢?
答案就是采用类vue这种MVVM框架的数据驱动思想,即让视图状态和视图绑定在一块儿,状态变动时,视图也能自动变动,这样就不用直接操做dom。
视图的动态更新具体是采用virtual dom技术实现,virtual DOM相信你们都已有了解,大概是这么个过程:
用JS对象模拟DOM树 -> 比较两棵虚拟DOM树的差别 -> 把差别应用到真正的DOM树上。
下面以官网的一幅图来讲视图动态更新的过程:
// wxml <view>{{msg}}</view> // js data: { msg: 'Hello World' }
上面说明了视图如何更新的,其实在数据响应的过程当中,还有最重要的一环,即业务逻辑层的如何将变化的数据同步到视图层呢,这就涉及到双线程的通讯了,具体能够参考从微信小程序开发者工具源码看实现原理(三)- - 双线程通讯