热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

小程序底层实现原理及思考————————–引用

   1.单线程 


 


   1. 单线程

     1.1 改造Vue

      

对Vue进行改造通常有两种方案:


  • 使用类似polyfill的手法覆盖一些Vue原生提供的API


  • Fork一个Vue出来自己改


第一个方案能力有限,有一些Vue内部的逻辑没有办法通过polyfill的形式更改。第二种方案的缺陷是如果我只想修改Vue中的某一块逻辑,其他我不修改的部分如果有Bug,Vue官方更新了版本我没有办法同步。

这两种方案都有缺陷和不足,所以我没有使用这两个其中的任何一个,我使用了另一个方案,我觉得应该是目前为止最好的一种方案。

我简单介绍一下这种方案:


  • 把Vue.js装到node_modules里


  • 项目里使用webpack,并设置上别名


  • 把自己想改造的那部分代码copy到自己的项目目录中进行修改


因为我需要对渲染层进行改造,所以我需要重设web这个别名,如下:

 


  1. const path = require(‘path‘)


  2.  


  3. module.exports = {


  4. ‘vue$‘: path.resolve(__dirname, ‘../src/web/entry-runtime-with-compiler‘),


  5. compiler: ‘vue/src/compiler‘,


  6. core: ‘vue/src/core‘,


  7. shared: ‘vue/src/shared‘,


  8. web: path.resolve(__dirname, ‘../src/web‘),


  9. weex: ‘vue/src/platforms/weex‘,


  10. server: ‘vue/src/server‘,


  11. sfc: ‘vue/src/sfc‘


  12. }


    大致原理是:如果import了一个不需要改造的,那其实是import了node_modules里的原始Vue的文件,如果是import了需要改造的,那其实import的是我的目录,文件也是我修改后的文件。

1.2 为标签设置黑名单

     Vue通过一系列计算之后最终产出的结果是一些指令,比如创建一个DOM元素,移除一个DOM元素,插入到某个位置等。

所以当时的做法就简单在创建DOM元素时,用tagName校验是否在黑名单中,如果在黑名单中就触发警告并怎么怎么样。

但其实这种做法只能是:防君子不防小人。

1.3 遇到的问题

项目做到这里遇到一个问题是不论怎样,都没有办法防止开发者做一些我们想禁用的功能。因为是一个网页,开发者可以执行JS,可以操作DOM,可以操作BOM,可以做一切事情。

所以我们开始考虑将用户的代码放到一个绝对安全的沙箱中去执行。

2. 双线程

在Web技术下,可以将用户的代码放到Web Worker中去执行,也可以放到一个隐藏的iframe中去执行,或者宿主环境提供一个环境。无论怎样,目的都是相同的,就是把用户的代码放到一个绝对安全的沙箱中执行。

由于开发者是基于Vue.js开发,用户的代码是没有办法单独放到沙箱中去执行的,所以我把Vue也放到沙箱中去执行。

这个时候技术架构和技术方向被调整成了Master-Slave的双线程模式。

沙箱中的代码我称为Master,它通过一系列计算,最终输出一些指令:创建元素、修改元素、插入元素、路由跳转、事件绑定等一些基础指令。这些指令从沙箱中通过线程间的消息机制传递到网页中,这个网页有一个模块叫做Slave,它负责监听Master发过来的指令并根据指令做指定操作。

 

技术分享图片

 

 

 

把Vue放到Web Worker中执行需要解决非常多的问题,比如:原本Vue直接对DOM的操作需要转换成向另一个线程发送指令,还有事件绑定问题,事件的Event对象问题,事件修饰符(event.preventDefault)问题,路由控制(双向的)问题,表单元素的双向绑定问题、ref问题等。因为线程间的消息传递只能传递字符串,所以很多东西就会变得非常麻烦。

不过这些具体的技术问题都是比较容易解决的,比较难的问题是两个:“性能”和“原生能力受限”

 

 

 

 

2.1 性能

       在这种架构下,当页面有大范围UI变化时(例如首次渲染),Logic线程需要往UI线程发送大量的指令,包括:创建DOM,插入DOM,绑定事件等,每条指令都是一个单独的跨线程的消息通信,当消息数量庞大时,性能问题就会暴露出来,而且非常明显。

如果去Chrome DevTools的Performance面板看,会发现UI线程其实很闲,但是渲染的就是很慢。因为消息传递的代价,而且每次encode与decode也都需要代价,我自己写DEMO时没发现问题,但是投放到生产环境下去渲染一个真实的组件时,就会发现性能问题非常明显。

这个架构下虽然有性能问题,但以我的能力想解决这个问题也并不是太困难。另一个问题(原生能力受限)是这个架构下永远都无法解决的一个问题。

2.2 原生能力受限

为了安全,需要把用户的代码放到沙箱中去执行,但因为想让用户使用Vue开发,所以Vue也得放到沙箱中,这就导致了一个无法解决的问题是:我们官方提供的原生组件,也会受限。

道理很简单,假设用户在模板中使用了一个官方原生提供的组件,那么这个组件一定是需要提前注册到Vue中的,那就代表,我们官方提供的组件,也得放在沙箱中,所以把我们自己也给限制住了。

导致我们官方想提供视频组件,音频组件就很困难,因为我们官方组件也不能碰DOM和BOM,就更不用说提供其他Native能力的组件了。

当然这个问题在不改架构的情况下还是有办法强行解决的,解决方案是这样的:

官方组件先在沙箱中注册一个Vue的组件,然后这个组件不去实现具体的功能,只是接收用户传递的数据,然后把数据传递给UI线程,然后UI线程有一个这个组件对应的真实的组件去做具体的功能并最终渲染到UI上。

技术分享图片

 

 

 

每一个组件都需要这样做,这对开发组件的同学非常不友好,他们需要理解这个小程序底层的架构是如何运行的,而且还会增加很多工作量。

这不是我想要的,我希望的是不管我底层是单线程还是双线程,对上层开发是无感知的。而且因为Vue.js是在沙箱中做各种操作,也不确定未来会不会有什么需求是无法满足的,技术风险太高了。

到了这一步,我已经慢慢的开始感受到,Vue已经成为瓶颈,它限制了我。

3. 回归单线程

不只是因为技术原因,也因为一些其他原因,比如风险太高,时间紧,最终决定先将方案切换回单线程,这样至少说可以保证这个项目不用延期。然后另一边再去慢慢调研并研究出一个技术方案可以解决之前遇到的所有问题。

似乎又回到了起点,因为单线程有单线程的问题,那就是Web技术太开放了,我们无法做到“安全”和对开发者进行“管控”。

不过我们还是在单线程模式下找到了可以提高一定安全性的方案,方案是通过ShadowDOM的Close模式把Body锁住。这样开发者自己的代码是无法操作DOM的,因为被我锁住了,但是开发者的Javascript是自由的。

技术分享图片

 

 

 

在这个架构下,开发者可以操作DOM,可以操作BOM,可以操作Vue.js,什么都可以干,但它无法直接操作被我锁住的ShadowDOM,想操作这个ShadowDOM,必须通过合法的途径操作,而这个ShadowDOM才是小程序用于展示的主要的窗口。

然后对于BOM上的某些危险API,会被提前禁用掉。

这个方案似乎解决了所有问题,但还是为未来留下了一点隐患,只要开发者的Javascript是自由的,你就永远无法知道他会用他的Javascript做什么。对于某些未知的漏洞,可能非常危险,这为日后留下了一个风险,事情会变成一场官方和开发者之间持久的攻防战。

无论怎样,这个方案解决了目前遇到的所有问题,这也为我留下了非常多的时间去研究真正的小程序应该怎样做。

4. 回归双线程

最终,我发现双线程才是正确的方向,只有把用户的代码放到沙箱里执行才能真正的做到:“安全”和“管控”。

不过这一次我决定不再使用Vue.js,我需要开发一个全新的框架来支持双线程这种模式。上一次的双线程之所以会失败,主要原因是上一次是UI线程比较轻,而Logic线程比较重,用户的代码,Vue.js,官方的组件都跑在Logic线程下,而这个线程只是一个JS运行时,所以我们的原生能力会受到限制。

而这一次我决定让UI重一点,Logic轻一点。只把用户的代码和框架的一部分下放到Logic线程,大部分操作和原生组件都放在UI线程下执行。

 

技术分享图片

 

 

 

Worker线程只是做一些计算然后把数据传递到UI线程,然后大部分工作都在UI下面执行,并且官方的组件在UI这边执行。

这样可以解决之前遇到的两个问题:性能和原生能力受限。

因为线程之间的消息通道只传递数据,而数据不会像绘制UI指令数量那么多,可以说根本不在一个数量级,性能问题解决了,而且不光解决了性能问题,还顺带着提升了性能,因为无论用户代码写的执行效率再怎么低,都不会卡死UI线程。

原生能力受限的问题也解决了,因为官方提供的组件根本不在这个线程下运行,安全和管控的问题也解决了。

5. 一些思考

之前我一直以为其他小程序是Native渲染的,而我是基于Web技术实现的,但偶然有一次看到一些资料,才发现,原来大家都是基于Web技术实现的小程序,而实现方式也大致相同,遇到的问题也都一样。

可能唯一的区别是,我不需要多个webview,我只需要一个网页就够了,所以我可以把Logic线程的代码放到Web Worker里,而其他小程序是多个webview,所以他们不能用Web Worker,不过没有本质区别。

 

5.1 关于小程序跨平台的一些看法

前一段时间各种小程序多端框架满天飞,准确的说,这些框架都是“翻译”器,就我个人觉得,以翻译为基础在不同小程序之间进行跨端,只能算是一种临时性技术方案。

我觉得真正的终极技术方案,有两种:


  • 从渲染引擎层面抹平平台间的差异,例如:Flutter


  • 各个小程序开发商共同制定一些标准和规范,例如:浏览器


第一种,使用小程序提供的canvas的一些API实现一个渲染引擎,然后在渲染引擎上实现一些布局引擎,在此基础上提供的框架和其他能力都是统一的,不同平台之间只需要实现不同的渲染引擎即可。不过我不确定小程序提供的canvas能不能做到这一点,不过Web浏览器提供的canvas可以做到,像SpriteJS就做到了。

第二种,各大小程序厂商共同制定一套标准,按照标准实现各自的API,这种情况是比较好的,而且也不是完全没有可能。最近各大小程序厂商已经在W3C起草了小程序白皮书。

我捡重要的列一下白皮书中的内容:


  • 标准化小程序包(就是说一份小程序代码,可以在各大小程序平台解析,使用统一的.ma后缀的文件)


  • 标准化小程序页面的URI Scheme(就是说定义一份协议,然后同一个URI地址可以在不同的小程序平台打开相同的页面)


  • 标准化小程序Widgets



6. 总结

仔细看到这里的读者应该会对我开发小程序的整个过程和一些决策有一个大致的了解。

大家对小程序的底层实现都是使用双线程模型,大家对外宣称都会说是为了:


  • 方便多个页面之间数据共享和交互


  • 为native开发者提供更好的编码体验


  • 为了性能(防止用户的JS执行卡住UI线程)


  • 其他好处


但其实真正的原因其实是:“安全”和“管控”,其他原因都是附加上去的。

因为Web技术是非常开放的,Javascript可以做任何事。但在小程序这个场景下,它不会给开发者那么高的权限:


  • 不允许开发者把页面跳转到其他在线网页


  • 不允许开发者直接访问DOM


  • 不允许开发者随意使用window上的某些未知的可能有危险的API


当然,想解决这些问题不一定非要使用双线程模型,但双线程模型无疑是最合适的技术方案。

小程序底层实现原理及思考--------------------------引用




推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文对SQL Server系统进行了基本概述,并深入解析了其核心功能。SQL Server不仅提供了强大的数据存储和管理能力,还支持复杂的查询操作和事务处理。通过MyEclipse、SQL Server和Tomcat的集成开发环境,可以高效地构建银行转账系统。在实现过程中,需要确保表单参数与后台代码中的属性值一致,同时在Servlet中处理用户登录验证,以确保系统的安全性和可靠性。 ... [详细]
  • 本文详细介绍了在MySQL中如何高效利用EXPLAIN命令进行查询优化。通过实例解析和步骤说明,文章旨在帮助读者深入理解EXPLAIN命令的工作原理及其在性能调优中的应用,内容通俗易懂且结构清晰,适合各水平的数据库管理员和技术人员参考学习。 ... [详细]
  • 在当前的软件开发领域,Lua 作为一种轻量级脚本语言,在 .NET 生态系统中的应用逐渐受到关注。本文探讨了 Lua 在 .NET 环境下的集成方法及其面临的挑战,包括性能优化、互操作性和生态支持等方面。尽管存在一定的技术障碍,但通过不断的学习和实践,开发者能够克服这些困难,拓展 Lua 在 .NET 中的应用场景。 ... [详细]
  • 基于iSCSI的SQL Server 2012群集测试(一)SQL群集安装
    一、测试需求介绍与准备公司计划服务器迁移过程计划同时上线SQLServer2012,引入SQLServer2012群集提高高可用性,需要对SQLServ ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • Android UI设计:提升用户界面体验的关键要素
    在今年的谷歌I/O开发者大会上,北京时间5月19日凌晨1点,谷歌正式推出了Android 12 Beta 1系统,并公布了参与该版本开发者Beta测试计划的手机品牌。此次发布标志着Android 12在用户体验和界面设计上的重大改进,为开发者提供了更多创新工具和资源,以进一步提升用户的交互体验。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
  • 本文详细介绍了批处理技术的基本概念及其在实际应用中的重要性。首先,对简单的批处理内部命令进行了概述,重点讲解了Echo命令的功能,包括如何打开或关闭回显功能以及显示消息。如果没有指定任何参数,Echo命令会显示当前的回显设置。此外,文章还探讨了批处理技术在自动化任务执行、系统管理等领域的广泛应用,为读者提供了丰富的实践案例和技术指导。 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
author-avatar
mobiledu2502916457
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有