使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了;但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能会解释我们在开发过程中遇到的一些疑惑,比如为啥小程序不能操作dom、小程序是web技术渲染还是native技术渲染等等,另一方面对于我们个人成长也是有帮助的。

首先声明下,文章查看小程序开发者工具源码的方法仅限学习使用。

本文将从以下几个方面来说一下小程序的实现原理

如何查看小程序开发者工具源码

下面我们通过微信小程序开发者工具的源码来说说小程序的底层实现原理。以开发者工具版本号State v1.02.1904090的源码来窥探小程序的实现思路。如何查看微信源码,对于mac用户而言,查看微信小程序开发者工具的包内容,然后进入Contents/Resources/app.nw/js/core/index.js,注释掉如下代码就可以查看开发者工具渲染后的代码。

// 打开 inspect 窗口
if (nw.App.argv.indexOf('inspect') !== -1) {
tools.openInspectWin()
}

然后重启小程序开发者工具,就出现如下左侧页面,点击其中一个页面就能看到view层的dom结构,如下图右侧。

|

小程序架构设计

小程序的架构设计与web技术还是有一定的差别,其吸取了web技术的一些优势,同时也摒弃web技术中体验等不好的地方。下面通过问题的形式来说说小程序架构中的一些设计点。

1、小程序渲染是在同一个线程吗?双线程机制

开发过小程序的都知道,小程序是双线程设计,即视图渲染与业务逻辑分别在运行在不同的线程中。这个设计主要是解决web技术中的一个痛点:

web页面开发渲染线程和脚本线程是互斥的,长时间的脚本运行可能会导致页面失去响应或者白屏,体验糟糕。

小程序为了更好体验,将页面的渲染线程和脚本线程分开设计在不同线程中执行,具体实现:

  • 视图view层在webview中渲染,一个页面对应一个webview
  • 业务逻辑Appservice层运行在同一个JSCore线程中,具体ios是JavaScriptCore,android是X5 JSCore,开发者工具是webview中;

这样解决了长时间的脚本阻塞页面渲染的情况,但是也带来一些新的问题:

  • 天生的延迟,线程间要通信
  • 业务逻辑层因为运行在JSCore中无法访问DOM和BOM的api;

开发者工具使用webview加载业务逻辑层的代码,虽然依赖的环境有DOM和BOM api,为了保持一致;小程序对所有的模块进行了局部化处理使其不能访问这些api。这样双线程通过native,开发者工具通过后台websocket服务充当二者消息中转媒介,并且提供一些基础功能。具体可以参考官网图:

2、小程序是web渲染吗?界面渲染机制

页面渲染的方式主要有三种:

  • 纯web渲染
  • 纯native原生渲染
  • Hybrid渲染,即web和native渲染结合

因为小程序的宿主环境是微信,不太可能使用纯native渲染,否则所有小程序需要跟微信一起编码发版。采用纯web渲染貌似是可行的,支持快速在线更新,通过加装最新资源到本地即可渲染;但是纯web渲染在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在web技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。所以小程序采用Hybrid方式渲染,用官网的描述如下:

界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的WebView去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个WebView的任务过于繁重。

既然采用Hybrid方式渲染,那么页面的渲染可能会用到原生native来渲染,什么情况会用到原生渲染呢?

答案是使用到小程序提供的mapvideocanvastextarea等组件,页面中原生渲染的渲染原理可以参考官网原生组件。但是在小程序开发者工具中原生组件是使用html标签来模拟实现的。具体可以看下一节的map组件渲染结果。

3、小程序是用web的html标签渲染吗?Exparser组件框架

上面说到小程序主要由成熟的web技术渲染,能否直接使用html提供的标签如div、table等组织页面呢,答案不可以。主要考量:

  • 管控与安全:web技术可以通过脚本获取修改页面敏感内容或者随意跳转其它页面
  • 能力有限,会限制小程序的表现形式
  • 标签众多,增加理解成本

所以,小程序不能直接使用html标签渲染页面,其提供了10多个内置组件来收敛web标签,并且提供一个JavaScript沙箱环境来避免js访问任何浏览器api。

既然小程序不能直接使用html标签来渲染页面,那它提供的如viewcover-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组件创建方式如下:

4、小程序可以操作dom吗?数据驱动

小程序为了管控与安全,提供一个JavaScript沙箱环境来运行JavaScript代码,js代码不能访问任何浏览器相关的接口,那就意味着js是不能操作dom和bom的,否则可能报错。小程序实现沙箱环境呢?即通过将业务逻辑封装到一个局部环境中,局部环境修改dom和bom的相关api指向。具体封装形式如下:

那么问题来了,小程序是怎么给业务代码加上以上封装的呢?其实很简单,在小程序开发者工具中有一个后台服务,访问小程序的每个模块的path时,后台服务会调用wrapSourceCodeInDefine方法将请求的JS文件的内容分别包裹在define域中,方法的代码如下图所示:

这里的define是小程序底层实现模块化的方法之一,还有一个是require方法;通过define来定义一个模块,require来引用一个define定义的模块。从上面小程序对业务模块代码的封装可以看出:

  • define定义的模块对传递了跟浏览器相关的接口同名的API,如window、document、localStroage等等

    可能有人会说通过Function('return this')()来访问全局作用域window对象,但是小程序堵死了这条路,重写了Functioneval重置为undefined。例如下图:

  • require在引用模块时只传递require、module、exports三个参数,那么其他参数值就为undefined,不能在业务代码中访问这些接口

可以看看require定义的源码:

在实际的微信环境,业务逻辑层运行在JSCore中,其没有浏览器相关的信息,访问dom无从谈起;但是小程序开发者工具使用webview来运行业务逻辑代码,它有dom相关接口;所以通过上面沙箱环境来统一使js无法操作dom。

业务代码无法访问dom,怎么实现页面动态更新呢?

答案就是采用类vue这种MVVM框架的数据驱动思想,即让视图状态和视图绑定在一起,状态变更时,视图也能自动变更,这样就不用直接操作dom。

视图的动态更新具体是采用virtual dom技术实现,virtual DOM相信大家都已有了解,大概是这么个过程如下图:

实际处理可以简单描述如下:

用JS对象模拟DOM树 -> 比较两棵虚拟DOM树的差异 -> 把差异应用到真正的DOM树上。

其中,virtual dom是通过内置的wcc可以将wxml转换为js对象形式,以此来表示DOM树结构。

下面以官网的一幅图来说视图动态更新的过程:

 // wxml
<view>{{msg}}</view> // js
data: {
msg: 'Hello World'
}

上面说明了视图如何更新的,其实在数据响应的过程中,还有最重要的一环,即业务逻辑层的如何将变化的数据同步到视图层呢,这就涉及到双线程的通信了,具体可以参考从微信小程序开发者工具源码看实现原理(三)- - 双线程通信

5、小程序基础库作用到底是什么?

我们在开发者工具开发小程序时,一般都会选择一个基础库,如小程序开发者工具选择界面:

小程序基础库是用JavaScript写的,但是我们并没有在我们的小程序中直接引用,那么我们是怎么使用基础库提供功能的呢?答案是:

微信宿主环境会提前内置基础库,打开小程序时会自动将基础库注入到小程序的视图层和业务逻辑层中,小程序开发者工具则是由底层HTTP服务负责注入。

下图是小程序底层HTTP服务通过script脚本注入的相关代码:

小程序基础库功能包括两个部分视图层的WAWebview.js和业务逻辑层的WAService.js。下面就简单说下对应功能:

WAService为业务逻辑层提供基础功能

下看看一下WAService.js源码内容缩略图:

从源码可以看出基础库提供的WAService.js有很多功能,主要包括以下几部分

  • WeixinJSBridge:消息通信的统一封装易于调用,主要微信环境与native,开发环境与开发者工具后台服务的通信。
  • wx: wx对象下面的api方法封装
  • appServiceEngine:定义了全局的方法如define,require, App,Page,Component,getApp,getCurrentPages等
  • virtualDOM: VirtualDOM,Diff和Render UI实现
  • expraser: expraser框架组件的方法定义,这意味着逻辑层也具有一定的组件树组织能力。
  • Reporter: 小程序日志组件

WAWebview为视图层提供基础功能

小程序基础库为视图层提供的基础功能有些与WAService相同,主要功能如下:

  • 消息通信封装为WeixinJSBridge
  • 日志组件Reporter封装
  • wx对象下的api,跟WAService里的不同的是其大部分都是处理UI显示相关的方法
  • 小程序Expraser组件框架的实现和内置组件的注册
  • VirtualDOM,Diff和Render UI实现
  • 定义页面相关事件触发

参考文献

从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计的更多相关文章

  1. 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局

    从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...

  2. 从微信小程序开发者工具源码看实现原理(三)- - 双线程通信

    文章概览: 引言 小程序开发者工具双线程通信的设计 1.on: 用来收集小程序开发者工具触发的事件回调 2.invoke:以api方式调用开发工具提供的基础能力 3.publish:用来向Appser ...

  3. 从微信小程序开发者工具源码看实现原理(二)- - 小程序技术实现

    wxml与wxss的转换 1.wxml使用wcc转换 2.wxss使用wcsc转换 开发者工具主入口 视图层页面的实现 视图层页面实现技术细节 视图层快速打开原理 视图层新打开页面流程 业务逻辑层页面 ...

  4. jQuery 源码解析一:jQuery 类库整体架构设计解析

    如果是做 web 的话,相信都要对 Dom 进行增删查改,那大家都或多或少接触到过 jQuery 类库,其最大特色就是强大的选择器,让开发者脱离原生 JS 一大堆 getElementById.get ...

  5. 11.Spark Streaming源码解读之Driver中的ReceiverTracker架构设计以及具体实现彻底研究

    上篇文章详细解析了Receiver不断接收数据的过程,在Receiver接收数据的过程中会将数据的元信息发送给ReceiverTracker:   本文将详细解析ReceiverTracker的的架构 ...

  6. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

  7. 微信小程序一键生成源码 在线制作定制功能强大的微信小程序

    微信小程序发展到现在,短短的一年不到的时间(很快就要迎来微信小程序周年庆),在快迎来周年庆之际,百牛信息技术bainiu.ltd特记录一下这个发展的历程,用于将来见证小程序发展的辉煌时刻,我们还能知道 ...

  8. 解决微信小程序开发者工具输入框焦点问题

    Windows10笔记本上运行微信小程序开发者工具,输入框(input,textarea)没有焦点,只能在真机调试,效率太低.后来发现是Window10对笔记本高分屏支持不好,要DPI缩放,导致兼容性 ...

  9. Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)

                                                                                                        ...

随机推荐

  1. jquery.cookie.js用法详解

    创建一个会话cookie: $.cookie(‘cookieName’,'cookieValue’); 注:当没有指明cookie时间时,所创建的cookie有效期默认到用户浏览器关闭止,故被称为会话 ...

  2. WPF Binding妙处-既无Path也无Source

    <Window x:Class="XamlTest.Window12"        xmlns="http://schemas.microsoft.com/win ...

  3. XE Delphi 判断字符为中文的方法

    在uses中添加System.AnsiStrings /// Param ch--字符串/// Param cno--字符位置 function IsZHChar(const ch: AnsiStri ...

  4. laravel在wamp中输入地址后总是无法访问

    在wamp中的apache中conf的httpd.conf #LoadModule rewrite_module modules/mod_rewrite.so 改为 LoadModule rewrit ...

  5. 谷歌将为 Mac 和 Windows 用户推出新的备份和同步应用

    据报道,谷歌将于 6 月 28 日面向 Mac 和 Windows 用户发布一款新的备份和同步应用(Backup and Sync app). Google 刚刚宣布将推出其备份和同步应用程序,该工具 ...

  6. UWP项目生成错误: 未能使用“CompileXaml”任务的输入参数初始化该任务。“CompileXaml”任务不支持“PlatformXmlDir”参数。请确认该参数存在于此任务中,并且是可设置的公共实例属性。

    UWP项目生成错误: 未能使用“CompileXaml”任务的输入参数初始化该任务.“CompileXaml”任务不支持“PlatformXmlDir”参数.请确认该参数存在于此任务中,并且是可设置的 ...

  7. C#通过HttpListener实现HTTP监听

    代码: using NLog; using System; using System.Diagnostics; using System.IO; using System.Net; using Sys ...

  8. [转载]关于Java reference的一篇博文

    不再额外的叨叨了,直接附上原地址: https://community.oracle.com/people/enicholas/blog/2006/05/04/understanding-weak-r ...

  9. VPS用来配置上网外,还可以做一个同步盘

    我曾经在一个活动的博文里说过,男人必须要有一个VPS和一个树莓派,VPS这个东西,以后会是中国男人的一种必备技能,今天又有一个小伙伴请教我VPS的用法,我就简单说说我目前使用的情况.首先我希望你能有点 ...

  10. 快速开发平台 WebBuilder 8.4 发布

    WebBuilder是一款强大,全面和高效的应用开发和运行平台.基于浏览器的集成开发环境,可视化和智能化的设计,能轻松完成常规应用和面向手机的移动应用开发.高效.稳定和可扩展的特点,适合复杂企业级应用 ...