热门标签 | 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


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

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




推荐阅读
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文总结了在开发中使用gulp时的一些技巧,包括如何使用gulp.dest自动创建目录、如何使用gulp.src复制具名路径的文件以及保留文件夹路径的方法等。同时介绍了使用base选项和通配符来保留文件夹路径的技巧,并提到了解决带文件夹的复制问题的方法,即使用gulp-flatten插件。 ... [详细]
  • 本文详细说明了在JavaScript中解决alert弹出窗口文本换行问题的方法。通过给alert弹出的文本添加换行符,可以实现在弹窗中显示多行文本的效果。同时,提供了相关代码示例和注意事项,帮助读者更好地理解和应用这一解决方法。 ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 小程序自动授权和手动接入的方式及操作步骤
    本文介绍了小程序支持的两种接入方式:自动授权和手动接入,并详细说明了它们的操作步骤。同时还介绍了如何在两种方式之间切换,以及手动接入后如何下载代码包和提交审核。 ... [详细]
  • uniapp开发H5解决跨域问题的两种代理方法
    本文介绍了uniapp开发H5解决跨域问题的两种代理方法,分别是在manifest.json文件和vue.config.js文件中设置代理。通过设置代理根域名和配置路径别名,可以实现H5页面的跨域访问。同时还介绍了如何开启内网穿透,让外网的人可以访问到本地调试的H5页面。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
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社区 版权所有