解析从小程序开发者工具源码看原理实现

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

下面我们通过微信小程序开发者工具的源码来说说小程序的底层实现原理。以开发者工具版本号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结构,如下图右侧。

小程序架构设计

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来渲染,什么情况会用到原生渲染呢?

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

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

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

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

所以,小程序不能直接使用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组件创建方式如下:

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对象,但是小程序堵死了这条路,重写了Function,eval重置为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实现
  • 定义页面相关事件触发

以上就是解析从小程序开发者工具源码看原理实现的详细内容,更多关于从小程序开发者工具源码看原理实现的资料请关注我们其它相关文章!

(0)

相关推荐

  • 微信小程序之数据绑定原理解析

    最近在帮朋友写个小程序,本人小白一枚,但是好在计算机科班出身,有些概念一看还是明白的,只是之前没实际写过程序.于是最近看了好多资料和视频,不得不说,对于小白来讲,还是有点难度,但是不大. 通过最近看资料和别人的视频,总结一下: 1.页面布局,先画好.都是盒子,需要几个盒子,你就先画几个盒子.比如下面这个页面: 红色盒子 浅蓝盒子 绿色盒子(这个绿色盒子里又可以切分成两个盒子:白色字体较大一个盒子,白色字体较小一个盒子) 蓝色盒子 2.数据先静态,后动态 简单的说就是刚开始,你可以直接先往页面里塞

  • JS实现监控微信小程序的原理

    原理 之前也做过浏览器web端的SDK数据埋点上报,其实原理大同小异:通过劫持原始方法,获取需要上报的数据,最后再执行原始方法,这样就能实现无痕埋点. 举个例子:我希望监控所有web页面的ajax请求,每次发送ajax,都需要在控制台打印出发送的url 平时我们开发,发送ajax一般用的都是封装好的库,例如jQuery,Axios等,然而这些库,底层仍然用的是浏览器原生的XMLHttpRequest对象,因此,我们只需要修改XMLHttpRequest对象即可 注意:由于JS的灵活性,修改原生方

  • 微信小程序事件流原理解析

    这篇文章主要介绍了微信小程序事件流原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.什么是事件? 事件是视图层到逻辑层的通讯方式: 事件可以将用户的行为,反馈到逻辑层进行处理: 事件可以绑定在组件上,触发事件后,就会执行逻辑层中对应的事件处理函数: 事件对象可以携带额外信息. 二.事件模型 事件分为事件捕获阶段.事件冒泡阶段.事件处理阶段 事件对象的属性: type:触发事件的类型 timestamp:触发事件当时的时间戳 targe

  • 微信小程序 下拉刷新及上拉加载原理解析

    这篇文章主要介绍了微信小程序 下拉刷新及上拉加载实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.下拉刷新的概念及应用场景. 概念: 下拉刷新是移动端更新列表数据的交互行为,用户通过手指在屏幕上子上而下的滑动,可以触发页面的下拉刷新,更新列表数据. 应用场景: 在移动端,数据列表是常见的页面效果,更新列表数据是最基本的页面需求,相比于按钮刷新,定时刷新来说,下拉刷新的用户体验方便友好,已经成为移动端刷新列表数据的最佳解决方案. 微信小

  • 微信小程序wxml列表渲染原理解析

    这篇文章主要介绍了微信小程序wxml列表渲染原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 列表渲染存在的意义 以电商为例,我们希望渲染5个商品,而又希望容易改变,我们就要在wxml中动态添加. <view> <block wx:for="{{products}}" wx:for-item="item" wx:key="index"> <view>{{

  • 微信小程序 冒泡事件原理解析

    在微信小程序的事件分为冒泡事件和非冒泡事件: 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递. 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递. WXML的冒泡事件列表: 类型 触发条件 最低版本 touchstart 手指触摸动作开始   touchmove 手指触摸后移动   touchcancel 手指触摸动作被打断,如来电提醒,弹窗   touchend 手指触摸动作结束   tap 手指触摸后马上离开   longpress 手指触摸后,超过350ms再离开

  • 微信小程序 轮播图实现原理及优化详解

    轮播图的创建 第一步:在页面创建swiper组件 第二步:编写js页面 注意事项:wx:for渲染我们js中的图片数组,item默认写法,获取我们的图片数组中的图片,可通过增加wx:for-item="it"来改变默认的item名 优化 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • 微信小程序button标签open-type属性原理解析

    这篇文章主要介绍了微信小程序button标签open-type属性原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 open-type (微信开放能力):合法值中的其中之一: getUserInfo说明:引导用户授权 而获取用户信息,可以从bindgetuserinfo回调中获取到用户信息 而按钮的bindgetuserinfo属性 说明:用户点击该按钮时,会返回获取到的用户信息,回调的detail数据与wx.getUserInfo返回的

  • 解析从小程序开发者工具源码看原理实现

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

  • 微信小程序支付前端源码

    本文实例为大家分享了微信小程序支付前端源码,供大家参考,具体内容如下 //index.js Page({ data: { }, //点击支付按钮进行支付 payclick: function () { var t = this; wx.login({ //获取code换取openID success: function (res) { //code = res.code //返回code console.log("获取code"); console.log(res.code); var

  • 解析Android框架之OkHttp3源码

    OkHttp流程图 OkHttp基本使用 gradle依赖 implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.squareup.okio:okio:1.15.0' /** *这里拿get请求来 * 异步的get请求 */ public void okhttpAsyn() { //设置超时的时间 OkHttpClient.Builder builder = new OkHttpClient.Builder

  • 解析Android框架之Volley源码

    Volley简单使用 我这里是以依赖架包的形式 ,大家也可以以gradle的形式进行依赖. 好了,接下来上代码了..... //获取volley的请求对象 RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext()); StringRequest stringRequest = new StringRequest(StringRequest.Method.GET, "http://www.baidu.com

  • 分析从Linux源码看TIME_WAIT的持续时间

    目录 一.前言 二.首先介绍下Linux环境 三.TIME_WAIT状态转移图 四.持续时间真如TCP_TIMEWAIT_LEN所定义么? 五.TIME_WAIT定时器源码 5.1.inet_twsk_schedule 5.2.具体的清理函数 5.3.先作出一个假设 5.4.如果一个slot中的TIME_WAIT<=100 5.5.如果一个slot中的TIME_WAIT>100 5.6.PAWS(Protection Against Wrapped Sequences)使得TIME_WAIT延

  • 从Linux源码看Socket(TCP)Client端的Connect的示例详解

    前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看下Client端的Socket在进行Connect的时候到底做了哪些事情.由于篇幅原因,关于Server端的Accept源码讲解留给下一篇博客. (基于Linux 3.10内核) 一个最简单的Connect例子 int clientSocket; if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

  • 详解从Linux源码看Socket(TCP)的bind

    目录 一.一个最简单的Server端例子 二.bind系统调用 2.1.inet_bind 2.2.inet_csk_get_port 三.判断端口号是否冲突 四.SO_REUSEADDR和SO_REUSEPORT 五.SO_REUSEADDR 六.SO_REUSEPORT 七.总结 一.一个最简单的Server端例子 众所周知,一个Server端Socket的建立,需要socket.bind.listen.accept四个步骤. 代码如下: void start_server(){ // se

  • Java从源码看异步任务计算FutureTask

    目录 了解一下什么是FutureTask? FutureTask 是如何实现的呢? FutureTask 运行流程 FutureTask 的使用 前言: 大家是否熟悉FutureTask呢?或者说你有没有异步计算的需求呢?FutureTask就能够很好的帮助你实现异步计算,并且可以实现同步获取异步任务的计算结果.下面我们就一起从源码分析一下FutureTask. 了解一下什么是FutureTask? FutureTask 是一个可取消的异步计算. FutureTask提供了对Future的基本实

  • Java同步锁Synchronized底层源码和原理剖析(推荐)

    目录 1 synchronized场景回顾 2 反汇编寻找锁实现原理 3 synchronized虚拟机源码 3.1 HotSpot源码Monitor生成 3.2 HotSpot源码之Monitor竞争 3.3 HotSpot源码之Monitor等待 3.4 HotSpot源码之Monitor释放 1 synchronized场景回顾 目标:synchronized回顾(锁分类–>多线程)概念synchronized:是Java中的关键字,是一种同步锁.Java中锁分为以下几种:乐观锁.悲观锁(

  • React Context源码实现原理详解

    目录 什么是 Context Context 使用示例 createContext Context 的设计非常特别 useContext useContext 相关源码 debugger 查看调用栈 什么是 Context 目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项目中是不是经常会用到 @connect(...)(Comp) 以及 <Pro

随机推荐