找回密码
 会员注册
查看: 17|回复: 0

关于小程序的一切,读这一篇就够了~

[复制链接]

4

主题

0

回帖

13

积分

新手上路

积分
13
发表于 2024-9-20 21:48:50 | 显示全部楼层 |阅读模式
1.小程序发展史1.1NativeApp在智能机刚兴起的时代,网络还不是很发达,网页浏览速度也很慢,以文字为主。市面上的应用以NativeApp为主。NativeApp是基于iOS或者安卓的原生应用,特点是开发成本高,迭代慢,但是性能和体验很好,消息推送及时,比如qq,微信等。1.2H52014年HTML5完成标准定制,他的设计目的是为了在移动设备上支持多媒体,引入了Video、Audio等技术。在网页上浏览视频变得很方便,特点是开发和发布成本低,打开方便,无需下载到本地,但是性能受浏览器的处理能力的限制,相较于原生App来说差了一些,消息推送不及时。1.3HybridAppHybridApp就是混合式的App,也就是在移动端原生应用的基础上,通过JSBrdige等方法,访问原生应用的API进行JS的交互,并通过WebView等技术实现HTML与CSS的渲染。WebView可以理解为嵌套了一个浏览器内核(比如webkit)的移动端组件。这种技术实现的应用一般都是跨平台的,并且维护起来比较容易,性能介于H5和原生应用之间。1.4小程序小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。——百度百科自2017年1月微信小程序正式发布以来,目前互联网上已经有多种小程序:微信小程序百度智能小程序支付宝小程序QQ小程序2017.12018.72018.92019.6基于小程序几乎相同的技术原理,以及小程序的方便快捷的特性,还衍生出了多款小程序,比如抖音小程序、快手小程序、京东小程序、美团小程序等,帮助各大厂商更好的为用户提供便捷的服务。2018年微信小程序“跳一跳”爆火,记得当年食堂排队打饭的时候很多同学都在玩,助力了微信小程序在用户中的扩张,也激发了其他厂商开发小程序的热潮。2.原理分析2.1双线程模型无论是微信小程序还是支付宝小程序还是百度智能小程序等等,他们的总体架构都是基于双线程的。其中用于处理业务逻辑的JS代码运行在单独的线程里,渲染层(template、css)则运行在另外一个单独的线程里。以微信小程序为例:双线程模型不同于单线程模型,逻辑层与渲染层的数据交互需要通过JSBridge,二者是通过发布订阅,基于当前比较比较著名的MVVM,来实现数据的双向绑定的,从而实现数据通信。这样我们在微信小程序中通过在逻辑层中setData来改变Model层的数据就能够实现视图数据的异步更新。以下是微信小程序的生命周期:2.2整体架构注:以下所有内容均围绕微信开发者工具展开。打开微信开发者工具的源代码,他是基于NW.js运行的,所以下图中的package.nw就是我们要重点钻研的对象:这里面有很多代码,都是经过混淆与压缩的,将代码在VSCode中打开,并安装Prettier-Codeformatter插件可以实现对源码的格式化。但此时代码中已经不具有语义化的变量了,只能通过API大致推断代码是干什么的。源码中有一个vendor文件夹是值得注意的,通过它可以快速新建一个示例项目,同时里面有一个十分重要的 2.17.0.wxvpkg 包,它是微信小程序的基础库,包含了下文所提及的WebService与WebView等逻辑层与渲染层的处理。2.2.1WAWebview小程序视图层基础库,提供视图层的基础能力:var__wxLibrary={ fileName:'WAWebview.js', envType:'WebView', contextType:'others', execStartate.now()};var__WAWebviewStartTime__=Date.now();var__libVersionInfo__={ "updateTime":"2020.4.410:25:02", "version":"2.10.4"};/***core-js模块*/!function(n,o,Ye){ ... },function(e,t,i){  varn=i(3),   o="__core-js_shared__",   r=n[o]||(n[o]={});  e.exports=function(e){   returnr[e]||(r[e]={})  } ...}(1,1);var__wxConfig;var__wxTest__=false;varwxRunOnDebug=function(e){ e()};/***基础模块*/varFoundation=function(i){ ...}]).default;varnativeTrans=function(e){ ...}(this);/***消息通信模块*/varWeixinJSBridge=function(e){ ...}(this);/***监听nativeTrans相关事件*/!function(){ ...}();/***解析配置*/!function(r){ ... __wxConfig=_(__wxConfig),__wxConfig=v(__wxConfig),Foundation.onConfigReady(function(){  m() }),n?__wxConfig.__readyHandler=A:d?Foundation.onBridgeReady(function(){  WeixinJSBridge.on("onWxConfigReady",A) }):Foundation.onLibraryReady(A)}(this);/***异常捕获(error、onunhandledrejection)*/!function(e){ functiont(e){  Foundation.emit("unhandledRejection",e)||console.error("Uncaught(inpromise)",e.reason) } "object"==typeofe&"function"==typeofe.addEventListener?(e.addEventListener("unhandledrejection",function(e){  t({   reason:e.reason,   promise:e.promise  }),e.preventDefault() }),e.addEventListener("error",function(e){  vart;  t=e.error,Foundation.emit("error",t)||console.error("Uncaught",t),e.preventDefault() })):void0===e.onunhandledrejection&Object.defineProperty(e,"onunhandledrejection",{  value:function(e){   t({    reasone=e||{}).reason,    promise:e.promise   })  } })}(this);/***原生缓冲区*/varNativeBuffer=function(e){ ...}(this);varWeixinNativeBuffer=NativeBuffer;varNativeBuffer=null;/***日志模块:wxConsole、wxPerfConsole、wxNativeConsole、__webviewConsole__*/varwxConsole=["log","info","warn","error","debug","time","timeEnd","group","groupEnd"].reduce(function(e,t){ returne[t]=function(){},e},{});varwxPerfConsole=["log","info","warn","error","time","timeEnd","trace","profile","profileSync"].reduce(function(e,t){ returne[t]=function(){},e},{});varwxNativeConsole=function(i){ ...}([function(e,t,i){ ...}]).default;var__webviewConsole__=function(i){ ...}([function(e,t,i){ ...}]);/***上报模块*/varReporter=function(i){ ...}([function(e,L,O){ ...}]).default;varPerf=function(i){ ...}([function(e,t,i){ ...}]).default;/***视图层API*/var__webViewSDK__=function(i){ ...}([function(e,L,O){ ...}]).default;varwx=__webViewSDK__.wx;/***组件系统*/varexparser=function(i){ ...}([function(e,t,i){ ...}]);/***框架粘合层**使用exparser.registerBehavior和exparser.registerElement方法注册内置组件*转发window、wx对象上到事件转发到exparser*/!function(i){ ...}([function(e,t){ ...},function(e,t){},,function(e,t){}]);/***VirtualDOM*/var__virtualDOMDataThread__=false;var__virtualDOM__=function(i){ ...}([function(e,t,i){ ...}]);/***__webviewEngine__*/var__webviewEngine__=function(i){ ...}([function(e,t,i){ ...}]);/***注入默认样式到页面*/!function(){ ... functione(){  vare=i('...');  __wxConfig.isReady?void0!==__wxConfig.theme&i(t,e.nextElementSibling):__wxConfig.onReady(function(){   void0!==__wxConfig.theme&i(t,e.nextElementSibling)  }) } window.document&"complete"===window.document.readyState?e():window.onload=e}();var__WAWebviewEndTime__=Date.now();typeof__wxLibrary.onEnd==='function'&__wxLibrary.onEnd();__wxLibrary=undefined;WAWebview主要由以下几个部分组件:Foundation:基础模块WeixinJSBridge:消息通信模块exparser:组件系统模块__virtualDOM__:VirtualDOM模块__webViewSDK__:WebViewSDK模块Reporter:日志上报模块(异常和性能统计数据)2.2.2WAService小程序逻辑层基础库,提供逻辑层基础能力:var__wxLibrary={ fileName:'WAService.js', envType:'Service', contextType:'App:Uncertain', execStartate.now()};var__WAServiceStartTime__=Date.now();(function(global){ var__exportGlobal__={}; var__libVersionInfo__={  "updateTime":"2020.4.410:25:02",  "version":"2.10.4" }; var__Function__=global.Function; varFunction=__Function__; /** *core-js模块 */ !function(r,o,Ke){ }(1,1); var__wxTest__=false; varwxRunOnDebug=function(e){  e() }; var__wxConfig; /** *基础模块 */ varFoundation=function(n){  ... }([function(e,t,n){  ... }]).default; varnativeTrans=function(e){  ... }(this); /** *消息通信模块 */ varWeixinJSBridge=function(e){  ... }(this); /** *监听nativeTrans相关事件 */ !function(){  ... }(); /** *解析配置 */ !function(i){  ... }(this); /** *异常捕获(error、onunhandledrejection) */ !function(e){  ... }(this); /** *原生缓冲区 */ varNativeBuffer=function(e){  ... }(this); WeixinNativeBuffer=NativeBuffer; NativeBuffer=null; varwxConsole=["log","info","warn","error","debug","time","timeEnd","group","groupEnd"].reduce(function(e,t){   returne[t]=function(){},e  },{}); varwxPerfConsole=["log","info","warn","error","time","timeEnd","trace","profile","profileSync"].reduce(function(e,t){  returne[t]=function(){},e },{}); varwxNativeConsole=function(n){  ... }([function(e,t,n){  ... }]).default; /** *Worker模块 */ varWeixinWorker=function(e){  ... }(this); /** *JSContext */ varJSContext=function(n){  ... }([  ... }]).default; var__appServiceConsole__=function(n){  ... }([function(e,N,R){  ... }]).default; varProtect=function(n){  ... }([function(e,t,n){  ... }]); varReporter=function(n){  ... }([function(e,N,R){  ... }]).default; var__subContextEngine__=function(n){  ... }([function(e,t,n){  ... }]); var__waServiceInit__=function(){  ... } function__doWAServiceInit__(){  vare;  "undefined"!=typeofwx&wx.version&(e=wx.version),__waServiceInit__(),e&"undefined"!=typeof__exportGlobal__&__exportGlobal__.wx&(__exportGlobal__.wx.version=e) } __subContextEngine__.isIsolateContext(); __subContextEngine__.isIsolateContext()||__doWAServiceInit__(); __subContextEngine__.initAppRelatedContexts(__exportGlobal__);})(this);var__WAServiceEndTime__=Date.now();typeof__wxLibrary.onEnd==='function'&__wxLibrary.onEnd();__wxLibrary=undefined;WAService基本组成:Foundation:基础模块WeixinJSBridge:消息通信模块WeixinNativeBuffer:原生BufferWeixinWorker:Worker线程JSContext:JSEngineContextProtect:JS保护的对象__subContextEngine__:提供App、Page、Component、Behavior、getApp、getCurrentPages等方法2.2.3虚拟DOM微信小程序在WAService里面实现了小程序的 __virtualDOM__,通过 __virtualDOM__ 模块,可以实现JS对象到DOM对象的映射。但是这个虚拟DOM通过diff和patch后并不是转换成原生的DOM元素,而是微信小程序里面自定义的DOM元素,这些DOM元素的操作通过Exparser模块来统一管理:在WAWebview中包含了所有的wx自定义标签:同时,__virtualDOM__ 模块提供了很多的基础API,比如:getAll:获取所有NodegetNodeById:根据Id获取NodegetNodeId:获取NodeIdaddNode:添加节点removeNode:删除节点getExparser:获取Exparser对象(基于WebComponent的shadowDOM模型,可以在JS环境中运行,所有与节点树相关的操作都依赖于他)...(更多的API定义可以在WAService.js里面去查询)2.2.4WeiXinJSBridgeWeixinJSBridge提供了视图层JS与Native、视图层与逻辑层之间消息通信的机制,提供了如下几个方法:里面最重要的便是on和invoke,通过on来注册事件,通过invoke来触发相应的事件。2.3微信开发者工具微信开发者工具中的小程序是跑在NW.js中的,这里是他的官方API文档:https://nwjs.readthedocs.io/en/latest/他是基于 Chromium 和 Node.js 的,因此我们编译后的虚拟DOM转换成真实DOM后,通过他来运行。2.3.1一些反编译技巧我们可以通过开发者工具,在Devtools里输入help可以得到很多指令:其中比较有用的是openVendor。这个函数可以打开当前项目的源码,其实也就是包含了wcc和wcsc编译工具的一个文件夹:有了这些文件之后,对我们之后的分析会很有帮助。我们可以将这些文件拷贝到一个单独的目录,在VSCode中打开该项目,并安装以下插件:这个插件可以将微信开发者工具中的所有以.wxvpkg结尾的文件进行解压缩。同时通过他来将 quickstart 中的 miniProgramJs.wxvpkg 进行解压,得到我们在开发者工具中的源码文件。2.3.2编译原理2.3.2.1wcc编译wxml微信小程序提供了wcc工具来编译wxml代码。通过上面得到的代码,我们可以实现对wxml的编译,以开发者工具创建的Demo项目中的首页为例:                       获取头像昵称   获取头像昵称   请使用1.4.4及以上版本基础库          {{userInfo.nickName}}      {{motto}} 经过以下指令编译:./wcc./quickstart/miniProgramJs.unpack/pages/index/index.wxml>index.js会得到JS描述文件:它会声明一个$gwx函数,通过它可以得到VirtualDOM。接着我们在这个文件里添加几行代码去调用它,并通过Node.js或者NW.js执行这个文件:vardata=$gwx('./quickstart/miniProgramJs.unpack/pages/index/index.wxml')();console.log(JSON.stringify(data,null,2));可以得到我们想要的最终的VirtualDOM结构:{ "tag":"wx-page", "children":[  {   "tag":"wx-view",   "attr":{    "class":"container"   },   "children":[    {     "tag":"wx-view",     "attr":{      "class":"userinfo"     },     "children":[      {       "tag":"wx-view",       "attr":{},       "children":[        "请使用1.4.4及以上版本基础库"       ],       "raw":{},       "generics":{}      }     ],     "raw":{},     "generics":{}    },    {     "tag":"wx-view",     "attr":{      "class":"usermotto"     },     "children":[      {       "tag":"wx-text",       "attr":{        "class":"user-motto"       },       "children":[        ""       ],       "raw":{},       "generics":{}      }     ],     "raw":{},     "generics":{}    }   ],   "raw":{},   "generics":{}  } ]}然后通过window.exparser.registerElemtent方法将这些tag转换成真实DOM:比如说,以上的wx-text就会被转换成类似于以下DOM:   {{这里是具体的文字内容}}2.3.2.2wcsc编译wxss同样的,以Demo项目里的index.wxss为例,运行一下指令:./wcsc./quickstart/miniProgramJs.unpack/pages/index/index.wxss-js-o./css.js可以将该文件从wxss格式的内容,转换成JS的内容:这里面会生成setCssToHead方法,用于将相应的css转换后(如rpx转px等等),通过style标签插入到文档的head里面。2.4通信原理小程序逻辑层和渲染层的通信会由Native(微信客户端)做中转,逻辑层发送网络请求也经由Native转发。视图层组件:内置组件中有部分组件是利用到客户端原生提供的能力,既然需要客户端原生提供的能力,那就会涉及到视图层与客户端的交互通信。这层通信机制在iOS和安卓系统的实现方式并不一样,iOS是利用了WKWebView的提供messageHandlers特性,而在安卓则是往WebView的window对象注入一个原生方法,最终会封装成WeiXinJSBridge这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。逻辑层接口:逻辑层与客户端原生通信机制与渲染层类似,不同在于,iOS平台可以往JavaScriptCore框架注入一个全局的原生方法,而安卓方面则是跟渲染层一致的。无论是视图层还是逻辑层,开发者都是间接地调用到与客户端原生通信的底层接口。一般微信小程序会对逻辑层接口做层封装后,才暴露给开发者,封装的细节可能是统一入参、做些参数校验、兼容各平台或版本问题等等。2.5启动机制小程序有冷启动与热启动两种方式:假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动。冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。小程序没有重启的概念:当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁。当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁。启动流程:3.总结小程序拥有接近原生App的体验。小程序并不是真正的 “无需下载”,只是小程序的体积很小,在当今高速的网络环境下能够快速下载,用户感知不到,更确切的来说是 “无感下载”。基于移动端布局的局限性,可以高效且简单的开发,迭代快速。小程序是双线程模型,逻辑层和渲染层分别运行在不同的线程中,通过JSBridge进行通信。在小程序里有实现专门的JSBridge来实现JS和Native的双向调用。wxml文件通过wcc编译:wxml=>JS=>VirtualDOM。wxss文件通过wcsc编译:wxss=>JS=>style。小程序其实也是一种hybrid技术,但是他围绕宿主应用,实现了更为强大的生态,提供更为便捷的服务。4.参考文献微信小程序技术原理分析浅谈小程序的历史发展和现状以及一些多端解决方案阿拉丁研究院-互联网行业:2020年小程序互联网发展白皮书一篇文章了解JsBridge
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 14:22 , Processed in 0.787100 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表