深入解析小程序中的的双线程模型

本篇文章带大家理解一下微信小程序中的双线程模型,聊聊什么是小程序的双线程模型?为什么小程序不使用浏览器的线程模型,而使用双线程模型,希望对大家有所帮助!

有过微信小程序开发经验的朋友应该都知道“双线程模型”这个概念,本文简单梳理一下双线程模型的一些科普知识,学识浅薄,若有错误欢迎指正。

我以前就职于「小程序·云开发」团队,在对外的一些培训和技术分享里经常被人问到这样一个问题:“微信小程序与 Web 网站在技术层面的主要区别是什么?”,在编程语言和范式上,小程序开发与 Web 前端开发非常相似(比如都用 JavaScript 语言、与 HTML/CSS 非常相似的 WXML/WXSS 等),可它却没有直接用原生的前端技术。【相关学习推荐:小程序开发教程

与 Web 网站相比,以微信为宿主的小程序更需要考虑安全、性能等因素,保障小程序不会对微信App本身产生安全隐患,同时要尽量达到接近原生应用的性能和用户体验。这是为什么小程序不直接用浏览器的线程模型,非要自己弄一套双线程模型最主要的两个原因。

那什么是小程序的双线程模型呢?

理解一个新概念或技术的最好的方法就是给它一个参照物,所以要搞清楚小程序的线程模型,首先要对浏览器的线程模型有一定的了解。

浏览器是多进程的

可能每个前端工程师在刚入行的时候都不止一次地被面试官问到“怎么理解前端的单线程?”,因为前端核心技能之一的 JavaScript 语言是单线程的,充分理解并掌握JS单线程的运作方式对一个前端工程师来说是最基本的要求。但是很多初学者容易走入的一个误区:错误地把 “JavaScript 单线程”理解为“浏览器单线程”。

事实上,浏览器内部架构很复杂,只不过在处理 GUI 渲染线程和 JavaScript 逻辑脚本线程上用了互斥、阻塞的管理模式,让一些开发者产生了误解。

以 Chrome 浏览器为例,点击右上角的设置按钮然后进入“更多工具”->“任务管理器”会看到这样的弹窗:

能看到Chrome 开启了多个进程,包括浏览器进程、网络进程、GPU 进程等,这些都是通用的进程。请注意,上图里有两个标签页进程,Chrome 为每个标签页开启了一个独立的渲染进程( Renderer Process ),每个进程之间的资源( CPU、内存等)和行为( UI、逻辑等)互不共享,所以即便某个标签页崩溃了也不会影响其他标签页。

而在每个标签页进程中,浏览器会把不同的工作交给对应的线程,比如 GUI 渲染线程负责把 HTML 渲染成可视化的 UI;JavaScript 引擎线程负责解析和运行 JavaScript 代码逻辑;定时触发器线程负责处理 setTimeout/setInterval 定时器等。

多说一句,这里有一个很容易搞混的地方,其实setTimeout/setInterval 并不是 JavaScript 语言的一部分,而是运行时(最初是浏览器,后来 Node.js 也支持)提供的能力。

GUI 渲染线程和 JavaScript 引擎线程是互斥的,JavaScript 在执行期间会阻塞 UI 的渲染,甚至如果脚本执行时间太长会由于页面长时间无响应然后崩溃,正是 GUI 渲染线程和 JavaScript 引擎线程之间的这种互斥、阻塞的线程管理方式,让一部分前端开发者以为浏览器是单线程的。

那为什么 JavaScript 被设计成单线程的呢?

JavaScript 祖师爷只用了 10 天就创造了这门语言,最初他的想法只是在浏览器中提供一些简单的脚本逻辑用来处理用户交互、DOM 操作等,所以从设计上必须遵循两点:

  • 语法简单;

  • 运行机制简单。

在语法上,JavaScript 借鉴了 Java,但是去除了很多复杂的设定,比如类型声明、模块体系(后来加入)等。

在运行机制上,JavaScript 并没有像 Java 那样提供多线程能力,最主要就是为了避免多线程操作 DOM 造成 UI 冲突。比如存在多个线程同时操作同一个 DOM,浏览器该如何判断最终的 UI 效果是采用哪个线程的结果?这是经典的线程安全(也称为线程同步)问题,在多线程编程领域有很多解决方案,比如加入锁机制,但这样却又带来了更多的复杂性,与 JavaScript 简单易用的设计初衷相违背。

这同时也解释了为什么 GUI 渲染线程与 JavaScript 引擎线程是互斥的:JavaScript 代码有修改DOM 的权限。

当 JavaScript 代码被执行时,GUI 渲染线程会被挂起,等待 JavaScript 引擎线程空闲时再被执行,以免在渲染期间被 JavaScript 重复地修改 DOM 造成不必要的渲染压力。采用互斥的模式等待 JavaScript 代码执行完毕后,可以保证渲染是最终的执行结果。所以浏览器的空闲(Idle)时长也成了衡量网站性能的重要指标之一,空闲时长多代表 JavaScript 逻辑不密集以及 DOM改动频率低,这种情况下浏览器可以更快速顺畅地响应用户的交互行为,如下图:

React Fiber就是利用idle时间进行分片任务处理。

后来,HTML5 引入了 Web Worker,提供多线程执行 JavaScript 代码的能力,但是与其他编程语言不同的是,Worker 线程与主线程并不是平行的,而是一种主从( Master-Slave)多线程模型。

Worker 内的 JavaScript 代码不能操作 DOM,可以将其理解为线程安全的。要记住这一点,这是后面讲小程序双线程模型一个重要的基础。

那么为什么微信小程序不直接使用浏览器的线程模型呢?这需要从产品和技术两个角度对比小程序与 Web 网站的差异。

为什么小程序不使用浏览器的线程模型

我刚接触小程序开发时,经常“嫌弃”它跟 Web 相比阉割弱化的能力、跟 Vue 相比简单到过分的语法等。当时,我几乎觉得小程序就是微信仗着自己庞大的用户量搞技术垄断。

但是,随着对技术和产品的不断深入理解,我对小程序的态度也有了转变,由“嫌弃”变成了敬佩,因为在充分理解了小程序的产品定位后,我发现双线程模型是在小程序这类产品场景下的最优解。那小程序是一款什么样的产品呢?

小程序的宿主是微信,但是小程序版本的迭代是独立的,升级更新不依赖宿主,这一点跟 Web 网站是相同的。也就是说,小程序沿袭了 Web 的某些优势,但它并不是 Web,目前 Web 相关的技术已经相当全面,能够承载一些非常庞大的应用程序,比如 3D 地图、游戏等。

而小程序的定位是小而美、用完就走,不追求在微信中实现全部的 Web 能力,所以和 Web 来比能力上肯定差一些,同时具备一些微信提供的原生能力,比如原生组件、系统级别和微信生态的 API 等等。

另外,“小程序-微信”的关系跟“网站-浏览器”的关系不同,前者更接近 CodePen、JSFiddler 这类在线编程平台(课里简称平台)中每个程序案例(简称案例)与平台的关系。

从技术的角度上,平台最核心的一个考量点是为案例提供足够能力的前提下,保证案例的逻辑不会危及平台的安全。想象一下,假如你能够在 CodePen 上编写一个程序来获取 CodePen 的私密信息,可能第二天 CodePen 就崩溃然后炒掉所有员工。

在这样的产品基调下进行技术选型,接下来就是架构师和程序员的工作了。

还是以 CodePen 为例,假如让你来设计这样的编程平台,你会用什么技术呢?可能你第一个想到的是用 iframe,因为可以在 iframe 内使用全部 Web 能力。事实上 CodePen 确实用 iframe 来呈现程序的效果,但是并不会把输入的 JavaScript 代码完全拷贝到 iframe 内运行,而是代码会经过一次编译流程之后才会被注入 iframe 内。这样做的出发点主要是基于安全的考虑,在编译过程中将一些危险的代码剔除;其次这样做还能在平台中支持更多语言,比如typescript。当然,还有性能,性能问题是 iframe 老生常谈的问题了,我就不多说了。

所以,不仅要使用 iframe,还需要引入额外的 JavaScript 编译器。CodePen 一定要保证每个案例的 JavaScript 代码是线程安全的,最基本的就是要禁止程序操作CodePen 网站的 DOM ,实现这一点有两个方法:

  • 一个是 Web Worker;

  • 另一个是使用 Shadow DOM。

Web Worker 是线程安全的,Worker 内的 JavaScript 代码无法获取 Window 和 Document 对象,也就无法操作 DOM。除此之外,由于 Worker 的线程安全特性,Worker 内的代码运行过程中不会阻塞外层的 GUI 渲染线程,两者可以并行。

Shadow DOM 是 Web Components 规范的一部分,将 ShadowRoot 的模式设置为 closed 就可以禁止获取到 ShadowRoot 节点,从而也无法操作其内部的 DOM。

两者相比,Shadow DOM 的兼容性比 Web Worker 更差,距大规模使用的日期还很遥远,所以 Web Worker 的方案更现实一点。

这样就形成了一个简易的双线程模型:Worker 线程负责计算,将结果通过 postMessage 传递给主线程,主线程负责渲染。

但是这个模型存在比较严重的性能问题,Web Worker 非常耗费资源,除去计算消耗以外,与主线程的通信过程对性能的损耗也非常严重。

那有没有办法实现跟 Web Worker 一样的线程安全,同时又兼顾性能保证良好的用户体验呢?这便是微信小程序采用双线程模型的主要目的。

安全高效的双线程模型

虽然前面用了 CodePen 这类编程平台做类比,但小程序与 CodePen 的技术需求并不完全相同,主要区别在于小程序并不需要支持所有的 HTML 标签,只提供有限的几类 UI 组件,根据小程序产品定位,我们可以归纳出小程序的主要技术需求可以归纳为下面这样几点。(任何新技术或架构都是为了解决特定的问题,所以有必要了解小程序的主要技术需求。)

  • 限制 UI 组件类型,只允许声明指定的几个组件

    小程序在声明组件时并不是使用原生的 HTML 标签,而是只能够通过微信提供的几种内置基础组件,当然你也可以自定义组件,但也是通过对内置基础组件的组合来实现。

  • 保证逻辑线程安全,不允许直接操作 UI 组件

    小程序更新 UI 的方式与 Vue/React 等 MVVM 框架类似,JavaScript 代码不能直接操作 DOM(仅做类比,事实上小程序中没有DOM的概念),而是通过更新状态( setState )的方式异步更新 UI ,这个过程中会用到 VDOM 和高效的 diff 算法(这两点并不是我们要讨论的内容,你课下可以自己搜索相关资料)。

  • 能够在线更新,不依赖微信

    小程序的宿主是微信,如果使用纯 Native 实现,那么小程序的版本更新必须依赖微信,跟微信的代码一起发版,这样肯定是不行的。如果是纯 Web 实现,安全和性能就很难得到保障。

    小程序需要既能够像 Web 一样将资源托管在云端,更新独立;同时又能够保证足够好的安全性和性能。所以最终小程序采用了一种混合的架构模式:使用 Webview 渲染 UI、使用类似Web Worker 的独立线程运行逻辑,这就是双线程模型

  • 性能需尽量提升,保证用户体验

    前面提到的基于 Web Worker 的简易双线程模型性能是很大的问题,小程序的双线程模型并不是使用 Web Worker 子线程,而是一个独立的“主线程”,这样能够保证相对较好的性能。

渲染线程和逻辑线程

小程序的双线程指的就是渲染线程和逻辑线程,这两个线程分别承担UI的渲染和执行 JavaScript 代码的工作。如下图所示:

渲染线程使用 Webview 进行 UI 的渲染呈现。Webview 是一个完整的类浏览器运行环境,本身具备运行 JavaScript 的能力,但是小程序并不是将逻辑脚本放到 Webview 中运行,而是将逻辑层独立为一个与 Webview 平行的线程,使用客户端提供的 JavaScript 引擎运行代码,iOS 的JavaScriptCore、安卓是腾讯 X5 内核提供的 JsCore 环境以及 IDE 工具的 nwjs 。

逻辑线程是一个只能够运行 JavaScript 的沙箱环境,不提供 DOM 操作相关的 API,所以不能直接操作 UI,只能够通过 setData 更新数据的方式异步更新 UI。

事件驱动的通信方式

注意上图渲染线程和逻辑线程之间的通信方式,与 Vue/React 不同的是,小程序的渲染层与逻辑层之间的通信并不是在两者之间直接传递数据或事件,而是由 Native 作为中间媒介进行转发

整个过程是典型的事件驱动模式:

  • 渲染层(也可以称为视图层)通过与用户的交互触发特定的事件 event;

  • 然后 event 被传递给逻辑层;

  • 逻辑层继而通过一系列的逻辑处理、数据请求、接口调用等行为将加工好的数据 data 传递给渲染层;

  • 最后渲染层将 data 渲染为可视化的 UI。

这种数据驱动 UI 的模式是现在前端编程领域较为推崇的编程范式,如果你是一个超过 5 年开发经验的前端开发者的话,那么我相信在最初接触到这种模式的时候肯定有一些不适应,因为在此之前 JavaScript 操作 DOM 几乎是一种“业内规则”,甚至有不少针对前端入门的图书、博客和教材都是先从 DOM 操作讲起,现在看来这些确实有些不合时宜了。

而这样逻辑与渲染分离的线程分工模式一方面能够保证运行在逻辑线程沙箱内的 JavaScript 代码是线程安全的,另一方面由于渲染线程的计算量非常小从而保证了对用户交互行为的快速响应,提高了用户体验。

总的来说,跟浏览器的线程模型相比,小程序的双线程模型解决了或者说规避了 Web Worker 堪忧的性能同时又实现了与 Web Worker 相同的线程安全,从性能和安全两个角度实现了提升。可以概括地说,双线程模式是受限于浏览器现有的进程和线程管理模式之下,在小程序这一具体场景之内的一种改进的架构方案

总结

在我看来,程序员的核心能力和竞争力并不是充分了解某种语言或框架的 API ,而是这些语言和框架底层的原理知识。对一个小程序的开发者来说,在工作中遇到技术难题时的解决方案往往是基于底层原理的(甚至更直白一点,当你找工作面试时,没人会问你小程序的语法)。

通过了解小程序双线程模型的背景、设计、通信,希望能够让大家更深入地理解小程序的底层架构,如果在后续工作中有类似场景的需求也可以作为借鉴。当然,了解小程序的双线程模型并不是唯一的目标,这些知识在一定程度上能对日常开发工作产生一些启示,主要是性能方面:

  • 在保证功能的前提下尽量使用结构简单的 UI;

  • 尽量降低 JavaScript 逻辑的复杂度;

  • 尽量减少 setData 的调用频次和携带的数据体量。

更多编程相关知识,请访问:编程视频!!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


概述 消息能力是小程序能力中的重要组成,我们为开发者提供了订阅消息能力,以便实现服务的闭环和更优的体验。 订阅消息推送位置:服务通知 订阅消息下发条件:用户自主订阅 订阅消息卡片跳转能力:点击查看详情可提爱转至该小程序的页面 消息类型 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务
判断H5页面环境在微信中还是小程序中 用小程序提供的wx.miniProgram.getEnv可以获取环境参数 <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.
wx.reLaunch和wx.navigateTo,wx.navigateTo的区别 2019-03-23 11:18:05 wx.navigateTo 用于保留当前页面、跳转到应用内的某个页面,使用 wx.navigateBack可以返回到原页面。对于页面不是特别多的小程序,通常推荐使用 wx.n
微信小程序如何从数组里取值_微信小程序 传值取值的几种方法总结 小程序里常见的取值有以下几种,一个完整的项目写下来,用到的概率几乎是100%。 列表index下标取值 页面传值 form表单取值 1. 列表index下标取值 实现方式是:data-index="{{index}}&quot
H5项目接入微信授权登录,通过 UA 区分微信还是普通浏览器: let ua = navigator.userAgent.toLowerCase(); let isWeixin = ua.indexOf('micromessenge
微信小程序获取data-xx=""属性的值,自定义属性设置和获取(data-) 微信小程序<view class="details-btn" data-taskId="111" bindtap='taskdetails&#39
小程序报错:TypeError: Cannot read property ‘addEventListener‘ of undefined 解决办法 将调试基础库由2.16.0(或者当前的) -> 2.14.1 解决问题
H5跳转微信小程序-成功案例(VUE)(踩坑无数) TuoMei 已于 2022-07-29 09:52:22 修改 准备工作 根据官方提供的资料需准备以下几点: 1、已认证的服务号 2、绑定JS接口安全域名 (在微信公众平台设置) 3、IP白名单 (在微信公众平台设置) 4、将小程序和H5公众号进
微信小程序 页面跳转和数据传递实例详解 这篇文章主要介绍了微信小程序 页面跳转和数据传递实例详解的相关资料,这里附有实例代码帮助到家学习理解,需要的朋友可以参考下 微信小程序 页面跳转和数据传递 1.先导 在Android中,我们Activity和Fragment都有栈的概念在里面,微信小程序页面也
情景1.拉取公司代码演示: 因为github有墙,这里我们以gitee(码云)为例作为演示 (其实就是国产github,也非常好用~) 步骤一:打开Git界面 先在一个空文件夹右击Git Bash Here,打开git界面 步骤二:输入克隆远程仓库指令 别人复制的链接在这里获取 拿到别人赋值的链接自
如何开发微信小程序? 作为一名10多年一直从事互联网平台开发的从业者,我来回答下这个问题吧。 微信小程序开发流程总体可以归纳为4个步骤, 老张带您捋一捋整个环节,小白用户可以收藏了。 好了废话不多说,开始! 一、开发前小程序需要准备的资料 我们在开发微信小程序前,需要准备下相关资料。这个资料主要是后
原生小程序开发优化方案 为了更好的制定优化方案,我们 有必要先了解下小程序的底层架构、以及与普通网页开发的差异 小程序最终渲染载体与当下一些热门的技术 Flutter、React Native等不同,依然是浏览器内核,而不是原生客户端。 而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中
1,不要下两倍尺寸的图片, 小程序本身自己就会对元素缩小两倍,设计图片的一杯就已经很清晰了。 2,图片压缩,(主要是压缩静态资源,ps 可以压缩,然后有一些在线压缩工具,保持600-800kb 的静态) 3,通用的代码组件化 4,是在工程量太大可以分包,分包现在最大支持20m(一般都不会去分包的)
文章浏览阅读189次。人工智能研究实验室OpenAI在2022年11月30日发布了自然语言生成模型ChatGPT,上线两个月就已经超过一亿用户,成为了人工智能界当之无愧的超级大网红。ChatGPT凭借着自身强大的拟人化及时应答能力迅速破圈,引起了各行各业的热烈讨论。简单来说ChatGPT就是可以基于用户文本输入自动生成回答的人工智能聊天机器人。那肯定会有人说这不就是Siri嘛,虽然都是交互机器人但是两者的差别可老大了。那么ChatGPT在人机交互时为什么会有这么出色的表现?它到底会不会取代搜索引擎?90%的人真的会因为ChatG
文章浏览阅读193次。8. 导航和路由管理:掌握小程序的导航方式,如使用wx.navigateTo跳转页面、使用wx.redirect重定向页面等,学会实现页面之间的跳转和传参。1. 小程序的基本概念和架构:了解小程序的定义、特点以及与传统APP的区别,掌握小程序的运行环境、组件和API等基本概念。10. 支付功能:学习小程序的支付方式,如微信支付、支付宝支付等,了解支付流程和注意事项,学会实现小程序的支付功能。9. 用户授权和登录:了解小程序的用户授权机制,如获取用户信息、调用微信API等,学会实现用户的登录和注册功能。_微信小程序开发知识点总结
文章浏览阅读4.8k次,点赞7次,收藏18次。一、准备工作1. 安装微信开发者工具,并登录微信小程序账号;2. 准备斗地主游戏的图片资源;3. 准备斗地主游戏的音效资源;二、创建小程序1. 打开微信开发者工具,点击“新建小程序”,输入小程序名称,选择小程序的项目目录,点击“创建”;2. 在小程序的项目目录中,新建文件夹“images”,将准备好的斗地主游戏的图片资源放入“images”文件夹中;3. 在小程序的项目目录中,新建文件夹“sounds”,将准备好的斗地主游戏的音效资源放入“sounds”文件夹中;三、编写代码1. 在小程_扑克牌微信小程序代码
文章浏览阅读3.9k次,点赞3次,收藏7次。一、准备工作:1. 安装微信开发者工具,创建小程序项目;2. 准备游戏角色图片;3. 准备游戏背景音乐;二、实现步骤:1. 创建游戏页面,添加游戏角色图片,添加游戏背景音乐;2. 创建游戏角色类,定义游戏角色属性,如角色名称、角色图片、角色能力等;3. 创建游戏类,定义游戏属性,如游戏人数、游戏角色、游戏规则等;4. 创建游戏控制类,定义游戏流程,如游戏开始、游戏结束、游戏角色分配等;5. 创建游戏界面,实现游戏流程,如游戏开始、游戏结束、游戏角色分配等;6. 创建游戏结果页面,显示游戏_微信小程序游戏代码
文章浏览阅读1.7k次。1. 创建小程序项目:使用微信开发者工具创建一个小程序项目,并在项目中添加一个页面,用于模拟聊天。 2. 定义数据结构:定义一个数据结构,用于存储聊天记录,包括发送者、接收者、消息内容等信息。 3. 实现聊天功能:实现聊天功能,包括发送消息、接收消息、显示消息等功能。 4. 实现界面:使用微信小程序的界面框架,实现聊天界面,包括聊天记录列表、输入框等。代码示例:// 定义数据结构var chatData = { sender: '', receiver: '', message: '' };_制作聊天对话小程序代码
文章浏览阅读2.1k次。1、创建小程序项目:使用微信开发者工具,新建一个小程序项目,输入项目名称,选择项目目录,点击“创建”按钮,即可创建小程序项目。2、添加页面:在小程序项目中,可以添加多个页面,每个页面都有自己的页面文件,比如首页、分类页、购物车页、我的页面等。3、添加组件:在小程序项目中,可以添加多个组件,比如商品列表组件、购物车组件、订单组件等,用于在页面中显示商品信息、购物车信息、订单信息等。4、添加接口:在小程序项目中,可以添加多个接口,用于获取商品信息、购物车信息、订单信息等,以便在页面中显示。5、_微信开发者工具做一个我的商城