原文来自微信官方账号——腾讯IMWeb前端团队,@希望
1。applet发展历史
1.1 Native App新智能机时代,网络不是很发达,网页浏览速度很慢,以文字为主。市面上的应用主要是Native App。
原生App是基于iOS或Android的原生应用,特点是开发成本高,迭代慢,但性能和体验好,消息推送及时,如qq、微信等。
2014年,HTML5完成了标准定制,设计目的是支持移动设备上的多媒体,引入了视频、音频等技术。
在网页上浏览视频非常方便,特点是开发发布成本低,打开容易,不需要本地下载。但性能受限于浏览器的处理能力,比原生App差一点,消息推送不及时。
1.3 Hybrid AppHybrid App是混合App,即在移动端原生应用的基础上,通过JSBrdige等方法接入原生应用的API与JS进行交互,通过WebView等技术实现HTML和CSS的渲染。
WebView可以理解为一个嵌套了浏览器内核(比如webkit)的移动组件。
通过该技术实现的应用程序通常是跨平台的,并且易于维护,性能介于H5和本机应用程序之间。
1.4 小程序小程序是无需下载安装即可使用的应用。它实现了应用“触手可及”的梦想,用户可以通过扫描或搜索打开应用。也体现了“用完即走”的理念,用户不必在意是否安装了太多应用。应用程序将无处不在,随时可用,但没有必要安装和卸载它们。——百度百科
自2017年1月微信小程序正式发布以来,网上出现了很多小程序:
|
|
|
|
2018年,微信小程序“跳跳”爆发。记得食堂排队吃饭的时候很多同学都在玩,这有助于微信小程序在用户中的扩张,也刺激了其他厂商开发小程序的热潮。
2。原理分析
2.1 双线程模型无论是微信小程序、支付宝小程序还是百度智能小程序等。,它们的整体架构是基于双线程的。
用于处理业务逻辑的JS代码在一个单独的线程中运行,而呈现层(模板,css)在另一个单独的线程中运行。
以微信小程序为例:
双线程模型不同于单线程模型。逻辑层和渲染层的数据交互需要JSBridge。两者都是基于目前众所周知的MVVM,通过发布和订阅实现数据的双向绑定,从而实现数据通信。
这样我们就可以通过微信小程序中逻辑层的setData改变模型层的数据来实现视图数据的异步更新。
以下是微信小程序的生命周期:
注:以下内容均围绕微信开发者工具展开。
打开微信开发者工具的源代码,它是基于NW.js的,所以下图中的package.nw是我们要重点关注的对象:
里面有很多代码,已经被混淆压缩了。打开VSCode中的代码,安装beauty-Code formatter插件来格式化源代码。但此时代码中并没有语义变量,所以我们只能通过API来大致推断代码是做什么的。
源代码中有一个vendor文件夹,可以快速创建一个新的样例项目。同时还有一个非常重要的包2.17.0.wxvpkg,是微信小程序的基础库,包含了下面提到的WebService、WebView等逻辑层和渲染层的处理。
applet视图层基本库提供了视图层的基本功能:
var _ _ wx library = { fileName:& # 39;WAWebview.js & # 39, env type:& # 39;WebView & # 39, context type:& # 39;其他& # 39;, execStart:date . now() }; var _ _ wawebviewstartime _ _ = date . now(); var _ _ libVersionInfo _ _ = { & # 34;更新时间& # 34;: "2020.4.4 10:25:02", & # 34;版本& # 34;: "2.10.4" }; /** * core-js模块 */ !函数(n,o,Ye) { ... },function(e,t,i) { var n = i(3), o = & # 34;_ _ core-js _ shared _ _ & # 34;, r = n[o]| |(n[o]= { }); e . exports = function(e){ return r[e]| |(r[e]= { }) } ... }(1,1); var _ _ wx config; var _ _ wx test _ _ = false; var wxRunOndebug = function(e){ e() }; /** *基本模块 */ var foundation = function(I){ ... }]。违约; var native trans = function(e){ ... }(这个); /** *消息通信模块 */ Varweixinjisbridge = function(e){ ... }(这个); /** *侦听与nativeTrans相关的事件 */ !function() { ... }(); /** *解析配置 */ !函数(r) { ... __wxConfig = _(__wxConfig),__wxConfig = v(__wxConfig),foundation . onconfig ready(function(){ m() }),n?__wxConfig。__readyHandler = A : d?foundation . onbridgeready(function(){ weixinsbridge . on(& # 34;onwxconfigdready & # 34;,A) }):foundation . onlibrary ready(A) }(this); /** *异常捕捉(error,onunhandldrejection) */ !function(e){ function t(e){ foundation . emit(& # 34;未处理的拒绝& # 34;,e)| | console . error(& # 34;未被捕获(在承诺中)& # 34;,e . reason) } & # 34;对象& # 34;e & & & # 34功能& # 34;== typeof e.addEventListener?(e . addevent listener(& # 34;未处理的拒绝& # 34;,function(e){ t({ reason:e . reason, promise: e.promise }),e.preventDefault() }),e . addevent listener(& # 34;错误& # 34;,函数(e){ var t; t = e.error,foundation . emit(& # 34;错误& # 34;,t)| | console . error(& # 34;未捕获& # 34;,t),e . prevent default() })):void 0 = = = e . onunhandldrejection & . object . define property(e,& # 34;onunhandledrejection & # 34,{ 值:函数(e) { t({ 原因:(e = e || {})。原因, promise:e . promise }) } }) }(这个); /* * * native buffer */ var native buffer = function(e){ ... }(这个); var weixinative buffer = native buffer; var native buffer = null; /** *日志模块:wxConsole、wxPerfConsole、wxNativeConsole、_ _ WebView Console _ _ */ VarwxConsole =[& # 34;日志& # 34;, "信息& # 34;, "warn & # 34, "错误& # 34;, "调试& # 34;, "时间& # 34;, "timeEnd & # 34, "集团& # 34;, "groupEnd & # 34].reduce(function(e,t){ return e[t]= function(){ },e },{ }); var wxPerfConsole =[& # 34;日志& # 34;, "信息& # 34;, "warn & # 34, "错误& # 34;, "时间& # 34;, "timeEnd & # 34, "跟踪& # 34;, "个人资料& # 34;, "profileSync & # 34].reduce(function(e,t){ return e[t]= function(){ },e },{ }); var wxNativeConsole = function(I){ ... }([函数(e,t,i) { ... }])。违约; var _ _ webview console _ _ = function(I){ ... }([函数(e,t,i) { ... }]); /** *报告模块 */ var reporter = function(I){ ... } ([function (e,l,o var Perf = function(I){ ... }([函数(e,t,i) { ... }])。违约; /** *视图层API */ var _ _ webview SDK _ _ = function(I){ ... }([function( var wx = _ _ webViewSDK _ _。wx; /** *组件系统 */ var expander = function(I){ ... } ([function (e,t,I /* * * Frame adhesive layer * *使用exparser.registerBehavior和exparser.registerElement方法注册内置组件 *将window和wx对象上的事件转发到expander function(i) { ... }([函数(e,t) { ... },function(e,t) {},function(e,t){ }]); /* * * Virtual DOM */ var _ _ virtualdomdatasthread _ _ = false; var _ _ virtual DOM _ _ = function(I){ ... }([函数(e,t,i) { ... }]); /* * * _ _ webview engine _ _ */ var _ _ webview engine _ _ = function(I){ ... }([函数(e,t,i) { ... }]); /** *将默认样式注入页面 */ !function() { ... 函数e(){ var e = I(& # 39;...'); __wxConfig.isReady?void0!== __wxConfig.theme && i(t,e . nextelementsibling):_ _ wx config . on ready(function(){ void 0!== __wxConfig.theme && i(t,e . nextelementsibling) }) } window . document & & # 34;完整& # 34;=== window.document.readyState?e():window . onload = e }(); var _ _ WAWebviewEndTime _ _ = date . now(); type of _ _ wx library . onend = = = & # 39;功能& # 39;& & _ _ wx library . onend(); _ _ wx library = undefined;WAWebview主要由以下组件组成:
applet逻辑层的基本库,提供逻辑层的基本功能:
var _ _ wx library = { fileName:& # 39;WAService.js & # 39, env type:& # 39;服务& # 39;, context type:& # 39;App:不确定& # 39;, execStart:date . now() }; var _ _ WAServiceStartTime _ _ = date . now(); (function(global){ var _ _ export global _ _ = { }; var _ _ libVersionInfo _ _ = { & # 34;更新时间& # 34;: "2020.4.4 10:25:02", & # 34;版本& # 34;: "2.10.4" }; var __Function__ = global。功能; var Function = _ _ Function _ _; /** * core-js模块 */ !函数(r,o,Ke) { }(1,1); var _ _ wx test _ _ = false; var wxRunOnDebug = function(e){ e() }; var _ _ wx config; /** *基本模块 */ var foundation = function(n){ ... } ([function (e,t,n){ var native trans = function(e){ ... }(这个); /** *消息通信模块 */ Varweixinjisbridge = function(e){ ... }(这个); /** *侦听与nativeTrans相关的事件 */ !function() { ... }(); /** *解析配置 */ !函数(i) { ... }(这个); /** *异常捕捉(error,onunhandldrejection) */ !函数(e) { ... }(这个); /* * * native buffer */ var native buffer = function(e){ ... }(这个); WeixinNativeBuffer = native buffer; native buffer = null; var wx console =[& # 34;日志& # 34;, "信息& # 34;, "warn & # 34, "错误& # 34;, "调试& # 34;, "时间& # 34;, "timeEnd & # 34, "集团& # 34;, "groupEnd & # 34].reduce(function(e,t){ return e[t]= function(){ },e },{ }); var wxPerfConsole =[& # 34;日志& # 34;, "信息& # 34;, "warn & # 34, "错误& # 34;, "时间& # 34;, "timeEnd & # 34, "跟踪& # 34;, "个人资料& # 34;, "profileSync & # 34].reduce(function(e,t){ return e[t]= function(){ },e },{ }); var wxNativeConsole = function(n){ ... }([函数(e,t,n) { ... }])。违约; /** * Worker模块 */ var weixin Worker = function(e){ ... }(这个); /* * * js context */ var js context = function(n){ ... }([ ... }])。违约; var _ _ appServiceConsole _ _ = function(n){ ... }([函数(e,N,R) { ... }])。违约; var Protect = function(n){ ... }([函数(e,t,n) { ... }]); var Reporter = function(n){ ... }([函数(e,N,R) { ... }])。违约; var _ _ sub context engine _ _ = function(n){ ... }([函数(e,t,n) { ... }]); var _ _ waServiceInit _ _ = function(){ ... } function _ _ doWAServiceInit _ _(){ var e; & # 34;未定义& # 34;!= type of wx & & wx . version & &(e = wx . version),__waServiceInit__(),& & & # 34未定义& # 34;!= type of _ _ export global _ _ & _ _ export global _ _。wx & &(_ _ export global _ _ . wx . version = e) } _ _ sub context engine _ _。isIsolateContext(); __subContextEngine__。isIsolateContext()| | _ _ doWAServiceInit _ _(); __subContextEngine__。initAppRelatedContexts(_ _ export global _ _); })(这个); var _ _ WAServiceEndTime _ _ = date . now(); type of _ _ wx library . onend = = = & # 39;功能& # 39;& & _ _ wx library . onend(); _ _ wx library = undefined;WAService的基本组件:
微信小程序在WAService中实现了小程序的___虚拟DOM __,通过__ virtual DOM __模块可以实现JS对象到DOM对象的映射。
但是这个虚拟DOM在diff和patch之后并没有转换成原生DOM元素,而是微信小程序中的自定义DOM元素,通过Exparser模块统一管理这些DOM元素的操作:
所有wx自定义标签都包含在WAWebview中:
同时,__virtualDOM__模块提供了许多基本的API,例如:
(更多API定义可在WAService.js查询)
2.2.4 WeiXinJSBridgeWeixinJSBridge提供了视图层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中输入帮助,我们可以得到很多说明:
其中比较有用的是openVendor。这个函数可以打开当前项目的源代码,这个项目实际上是一个包含wcc和wcsc编译工具的文件夹:
有了这些文件,对我们后面的分析会很有帮助。
我们可以将这些文件复制到一个单独的目录中,在VSCode中打开项目,并安装以下插件:
这个插件可以解压缩所有以结尾的文件。微信开发者工具中的wxvpkg。
同时通过他可以将quickstart中的miniprogramjs . wxvpkg解压到开发者工具中得到我们的源文件。
2.3.2 编译原理2.3.2.1 wcc 编译 wxml微信小程序提供wcc工具编译wxml代码。通过上面得到的代码,我们可以编译wxml,以developer tools创建的Demo项目主页为例:
& ltview class = & # 34集装箱& # 34;& gt & lt;view class = & # 34userinfo & # 34& gt & lt;block wx:if = & # 34;{ { canIUseOpenData } } & # 34& gt & lt;view class = & # 34userinfo-avatar & # 34;bindtap = & # 34bindViewTap & # 34& gt & lt;开放数据类型= & # 34;userAvatarUrl & # 34& gt& lt/open-data & gt; & lt;/view & gt; & lt;开放数据类型= & # 34;用户昵称& # 34;& gt& lt/open-data & gt; & lt;/block & gt; & lt;block wx:elif = & # 34;{{!hasUserInfo } } & # 34& gt & lt;button wx:if = & # 34;{ { canIUseGetUserProfile } } & # 34bindtap = & # 34getUserProfile & # 34& gt获取头像昵称
。/wcc。/quick start/miniprogramjs . unpack/pages/index/index . wxml & gt;Index.js将获得js描述文件:
它将声明一个$gwx函数,通过该函数可以获得虚拟DOM。然后我们在这个文件中添加几行代码来调用它,并通过Node.js或NW.js执行这个文件:
var data = $ gwx(& # 39;。/quick start/miniprogramjs . unpack/pages/index/index . wxml & # 39;)(); console . log(JSON . stringify(data,null,2));获得我们想要的最终虚拟DOM结构:
{ & # 34;标签& # 34;: "wx-page & # 34;, & # 34;儿童& # 34;:[ { & # 34;标签& # 34;: "wx-view & # 34;, & # 34;attr & # 34:{ & # 34;班& # 34;: "集装箱& # 34; }, & # 34;儿童& # 34;:[ { & # 34;标签& # 34;: "wx-view & # 34;, & # 34;attr & # 34:{ & # 34;班& # 34;: "userinfo & # 34 }, & # 34;儿童& # 34;:[ { & # 34;标签& # 34;: "wx-view & # 34;, & # 34;attr & # 34:{}, & # 34;儿童& # 34;:[ & # 34;请使用1.4.4及以上版本的基本库& # 34; ], & # 34;raw & # 34:{}, & # 34;仿制药& # 34;:{} } ], & # 34;raw & # 34:{}, & # 34;仿制药& # 34;:{} }, { & # 34;标签& # 34;: "wx-view & # 34;, & # 34;attr & # 34:{ & # 34;班& # 34;: "用户座右铭& # 34; }, & # 34;儿童& # 34;:[ { & # 34;标签& # 34;: "wx-text & # 34;, & # 34;attr & # 34:{ & # 34;班& # 34;: "用户座右铭& # 34; }, & # 34;儿童& # 34;:[ & # 34;" ], & # 34;raw & # 34:{}, & # 34;仿制药& # 34;:{} } ], & # 34;raw & # 34:{}, & # 34;仿制药& # 34;:{} } ], & # 34;raw & # 34:{}, & # 34;仿制药& # 34;:{} } ] }然后通过窗口把这些标签转换成真正的DOM。扩展器。registerelement方法:
例如,上面的wx-text将被转换成如下所示的DOM:
& ltspan & gt & lt;span style = & # 34显示:无& # 34;& gt& lt/span>。 & lt;span & gt{ {这里是具体的文字内容} } 2.3.2.2 wcsc编译wxss同样,以演示项目中的index.wxss为例,运行指令:
。/wcsc。/quick start/miniprogramjs . unpack/pages/index/index . wxss-JS-o ./CSS . JS可以将此文件的内容从wxss格式转换为JS内容:
这将生成setCssToHead方法,该方法用于转换相应的css(如rpx到px等。)并通过style标签插入到文档的头部。
2.4 通信原理小程序逻辑层和渲染层的通信会通过Native(微信客户端)进行转发,逻辑层发送的网络请求也会通过Native进行转发。
查看层组件:
一些内置组件利用了客户端本身提供的功能。由于需要客户端本身提供的功能,这将涉及视图层和客户端之间的交互通信。这种通信机制的实现在iOS和Android之间是不同的。iOS利用了WKWebView的messageHandlers特性,而Android则在WebView的window对象中注入了一个原生方法,最终会封装成类似WeiXinJSBridge的兼容层,主要提供invoke和monitor (on)两种方法。
逻辑层接口:
逻辑层与客户端的原生通信机制与渲染层类似,只是iOS平台可以在JavaScriptCore框架中注入一个全局原生方法,而Android方面与渲染层一致。
无论是视图层还是逻辑层,开发人员都是间接调用与客户端原生通信的底层接口。一般微信小程序在暴露给开发者之前,都会对逻辑层接口层进行封装。封装的细节可能是统一参数、参数验证、兼容各种平台或版本等等。
2.5 启动机制小程序有两种方式:冷启动和热启动:
小程序没有重启的概念:
启动流程:
https://mp.weixin.qq.com/s/5nQqBFFWwxtcf8S2Ba9PRA