微信小程序底层框架实现原理|万字长文(二)

简介: 微信小程序底层框架实现原理|万字长文

wxss 设计思路

WXSS 具有 CSS的大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。通俗的可以理解成基于CSS改了点东西,又加了点东西。

与 CSS 相比,WXSS 扩展的特性有:

  • 尺寸单位

rpx(responsive pixel) : 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

1686894047233.png

  • 样式导入
    使用 @import语句可以导入外联样式表, @import后跟需要导入的外联样式表的相对路径,用;表示语句结束。

编译

/**index.wxss**/
.test{
  height: calc(100rpx-2px);
  width: 200rpx;
}

如上我们定义的index.wxss,会被编译成js,注入webview

我们把编译后的js分成三部分,展开分析。

第一部分用于获取一套基本设备信息,包含设备高度、设备宽度、物理像素与CSS像素比例、设备方向。

/*********//*第一部分*//*设备信息*//*********/varBASE_DEVICE_WIDTH=750;// 基础设备宽度750varisIOS=navigator.userAgent.match("iPhone"); // 是否ipheone 机型vardeviceWidth=window.screen.width||375; // 设备宽度 默认375vardeviceDPR=window.devicePixelRatio||2; // 获取物理像素与css像素比例 默认2varcheckDeviceWidth=window.__checkDeviceWidth__||function() {
varnewDeviceWidth=window.screen.width||375// 初始化设备宽度varnewDeviceDPR=window.devicePixelRatio||2// 初始化设备 像素比例varnewDeviceHeight=window.screen.height||375// 初始化设备高度// 判断屏幕方向 landscape 为横向,如果是横向 高度值给宽度if (window.screen.orientation&&/^landscape/.test(window.screen.orientation.type||'')) newDeviceWidth=newDeviceHeight// 更新设备信息if (newDeviceWidth!==deviceWidth||newDeviceDPR!==deviceDPR) {
deviceWidth=newDeviceWidthdeviceDPR=newDeviceDPR  }
}
// 检查设备信息checkDeviceWidth()

第二部分:转化rpx

核心就是:下面两句,做了一个精度收拢

number = number /BASE_DEVICE_WIDTH *** (newDeviceWidth || deviceWidth);**

number =Math.floor(number + eps);

/*********//*第二部分*//*转化rpx*//*********/vareps=1e-4;//0.0001vartransformRPX=window.__transformRpx__||function(number, newDeviceWidth) {
// 如果0 返回 0  0rpx = 0pxif ( number===0 ) return0;
// px = rpx值 / 基础设备宽度750 * 设备宽度number=number/BASE_DEVICE_WIDTH* ( newDeviceWidth||deviceWidth );
// 返回小于等于 number + 0.0001的大整数,用户收拢精度number=Math.floor(number+eps);
if (number===0) {// 如果number == 0,说明输入为1rpxif (deviceDPR===1||!isIOS) {// 非IOS 或者 像素比为1,返回1return1;
    } else {
return0.5; 
    }
  }
returnnumber;
}

第三部分主要是 setCssToHead 顾名思义

/*********//*第三部分*//*setCssToHead*//*********/window.__rpxRecalculatingFuncs__=window.__rpxRecalculatingFuncs__|| [];
var__COMMON_STYLESHEETS__=__COMMON_STYLESHEETS__|| {} %svarsetCssToHead=function(file, _xcInvalid, info) {
varCa= {};
varcss_id;
varinfo=info|| {};
var_C=__COMMON_STYLESHEETS__functionmakeup(file, opt) {
var_n=typeof(file) ==="string";
if (_n&&Ca.hasOwnProperty(file)) return"";
if (_n) Ca[file] =1;
varex=_n?_C[file] : file;
varres="";
for (vari=ex.length-1; i>=0; i--) {
varcontent=ex[i];
if (typeof(content) ==="object") {
varop=content[0];
if (op==0) res=transformRPX(content[1], opt.deviceWidth) +"px"+res;
elseif (op==1) res=opt.suffix+res;
elseif (op==2) res=makeup(content[1], opt) +res;
      } elseres=content+res    }
returnres;
  }
varstyleSheetManager=window.__styleSheetManager2__varrewritor=function(suffix, opt, style) {
opt=opt|| {};
suffix=suffix||"";
opt.suffix=suffix;
if (opt.allowIllegalSelector!=undefined&&_xcInvalid!=undefined) {
if (opt.allowIllegalSelector) console.warn("For developer:"+_xcInvalid);
else {
console.error(_xcInvalid);
      }
    }
Ca= {};
css=makeup(file, opt);
if (styleSheetManager) {
varkey= (info.path||Math.random()) +':'+suffixif (!style) {
styleSheetManager.addItem(key, info.path);
window.__rpxRecalculatingFuncs__.push(function(size) {
opt.deviceWidth=size.width;
rewritor(suffix, opt, true);
        });
      }
styleSheetManager.setCss(key, css);
return;
    }
if (!style) {
varhead=document.head||document.getElementsByTagName('head')[0];
style=document.createElement('style');
style.type='text/css';
style.setAttribute("wxss:path", info.path);
head.appendChild(style);
window.__rpxRecalculatingFuncs__.push(function(size) {
opt.deviceWidth=size.width;
rewritor(suffix, opt, style);
      });
    }
if (style.styleSheet) {
style.styleSheet.cssText=css;
    } else {
if (style.childNodes.length==0) style.appendChild(document.createTextNode(css));
elsestyle.childNodes[0].nodeValue=css;
    }
  }
returnrewritor;
}
setCssToHead([".", [1], "test{ height: calc(", [0, 100], "-2px); width: ", [0, 200], "; }\n", ])(typeof__wxAppSuffixCode__=="undefined"?undefined: __wxAppSuffixCode__);

setCssToHead 传的参数 是我们定义的wxcss,变成了结构化数据,方便遍历处理

index.wxss中写rpx单位的属性都变成了区间的样子[0, 100]、[0, 200]。其他单位并没有转换。这样的话就可以方便的识别哪里写了rpx单位

[".", [1], "test{ height: calc(", [0, 100], "-2px); width: ", [0, 200], "; }\n", ]

注入

在渲染层的一个的标签中,有很长的一串字符串,并且用eval方法执行。如果你仔细看的话,还是可以勉强分辨出,这个字符串正是我们前面编译出来的js转换成的。

这样就可以得知,编译后的代码是通过eval方法注入执行的。这样的话完成了WXSS的一整套流程。

同时我们也可以看到,是在修改pageFrame 的路径之后,初始化小程序样式配置文件之后,才开始注入样式文件


image.jpeg

Virtual Dom 渲染流程

微信开发者工具和微信客户端都无法直接运行小程序的源码,因此我们需要对小程序的源码进行编译。

代码编译过程包括本地预处理、本地编译和服务器编译。

为了快速预览,微信开发者工具模拟器运行的代码只经过本地预处理、本地编译,没有服务器编译过程,而微信客户端运行的代码是额外经过服务器编译的。

编译

<!--index.wxml--><viewclass="container">Weixin<textstyle="position:relative;">文本</text></view><buttonbindtap="test">按钮</button>

如上面这段简单的wxml文件,经过编译之后,被编译成了 1500 多行

全部代码都被包裹在$gwx函数中,编译后的WXML文件,以js的形式插入到了渲染层的

image.jpeg

但是在这个script标签中插入了$gwx函数之后并没有立即执行这个函数。

在渲染层的一个的<script>标签中,我们可以看到这段代码

var decodeName = decodeURI("./pages/index/index.wxml")
var generateFunc = $gwx(decodeName)

我们在控制抬手动执行$gwx()的返回值 generateFunc()函数

返回的树形结构,就是该页面wxml对应的js对象形式表示的dom树

e2e698b41d194d5355b81810a90ff0d.png

这是一个类似Virtual Dom的对象,交给了 WAWebview.js 来渲染成真实DOM

事件系统设计

核心在于,wxml和js文件在两个线程渲染,解析。事件如何绑定?

我们最开始在wxml文件中定义的事件绑定,其实转化成虚拟dom树结构之后,其实只是一个键值对,表明了某个dom上有绑定某个事件,并没有完成事件绑定。

1686919009425.jpg

WAWebview.js 处理虚拟dom树时,会去循环遍历attr属性,判断attr中的属性名是否为事件属性

if (n = e.match(/^(capture-)?(mut-)?(bind|catch):?(.+)$/))

如果是,通过addListener方法进行了事件绑定。

可以理解成,通过addListner方法监听tap事件,就相当于 window.addEventListener对mouseup方法的监听。

回调函数中对函数的event信息进行组装,并触发sendData方法。


1686919050361.jpg

sendData方法就是向逻辑线程发送event数据的方法。

下图是我们在逻辑层接收到的数据和准备发送的数据结构

1686919240277.jpg

可以看到数据结构是一样的,

目前在触发sendData方法之前这些逻辑的解析包括event参数的组装都是在渲染层的底层基础库WAWebview.js中完成的,也就是说还在渲染线程中。

事件

微信小程序中主要事件绑定:bind catch

bind /catch后可以紧跟一个冒号,其含义不变,如bind:tap catch:tap

catch 会阻止事件向上冒泡。

mut-bind 来绑定事件。一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发。

需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。

1686919270708.jpg

相关文章
|
1天前
|
JSON 小程序 前端开发
微信小程序框架(五)-全面详解(学习总结---从入门到深化)
微信小程序框架(五)-全面详解(学习总结---从入门到深化)
52 0
|
1天前
|
存储 缓存 JSON
微信小程序框架(四)-全面详解(学习总结---从入门到深化)
微信小程序框架(四)-全面详解(学习总结---从入门到深化)
154 0
|
1天前
|
小程序 JavaScript 前端开发
简述微信小程序原理
简述微信小程序原理
|
1天前
|
小程序 JavaScript
在使用微信小程序开发中用vant2框架中的Uploader 文件上传wx.uploadFile无反应和使用多图上传
网上有的说是bind:after-read="afterRead"的命名问题不支持-,但是我这儿执行了console.log("file",file);证明函数运行了。后来发现是multiple="true"原因开启了多图上传,如果是多图上传的话file就是数组了
82 2
|
1天前
|
小程序 前端开发 JavaScript
微信小程序MINA框架
微信小程序MINA框架
116 0
|
1天前
|
缓存 小程序 JavaScript
支付宝小程序性能优化原理及手段
支付宝小程序性能优化原理及手段
104 0
|
1天前
|
小程序 JavaScript 前端开发
微信小程序框架(二)-全面详解(学习总结---从入门到深化)
微信小程序框架(二)-全面详解(学习总结---从入门到深化)
95 0
|
1天前
|
存储 编解码 小程序
抖音小程序开发中遇见的坑点
在抖音小程序开发中,需注意10大坑点:遵守小程序限制与规范;解决兼容性问题;优化数据加载速度;适应分享功能限制;处理视频播放挑战;优化图片加载显示;管理资源文件;提升用户体验;考虑安全性;及时更新维护。通过测试、优化和遵循官方文档,可克服这些问题,打造优质小程序。
|
1天前
|
小程序 前端开发 API
小程序全栈开发中的多端适配与响应式布局
【4月更文挑战第12天】本文探讨了小程序全栈开发中的多端适配与响应式布局。多端适配涉及平台和设备适应,确保统一用户体验;响应式布局利用媒体查询和弹性布局维持不同设备的布局一致性。实践中,开发者可借助跨平台框架实现多平台开发,运用响应式布局技术适应不同设备。同时,注意兼容性、性能优化和用户体验,以提升小程序质量和用户体验。通过这些方法,开发者能更好地掌握小程序全栈开发。
|
1天前
|
小程序 前端开发 API
微信小程序全栈开发中的异常处理与日志记录
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的异常处理和日志记录,强调其对确保应用稳定性和用户体验的重要性。异常处理涵盖前端(网络、页面跳转、用户输入、逻辑异常)和后端(数据库、API、业务逻辑)方面;日志记录则关注关键操作和异常情况的追踪。实践中,前端可利用try-catch处理异常,后端借助日志框架记录异常,同时采用集中式日志管理工具提升分析效率。开发者应注意安全性、性能和团队协作,以优化异常处理与日志记录流程。

热门文章

最新文章