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

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

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月前
|
小程序 JavaScript 前端开发
小程序常见的UI框架
小程序常见的UI框架
247 60
|
2月前
|
移动开发 小程序 JavaScript
开源的微信小程序框架
【8月更文挑战第22天】开源的微信小程序框架
144 65
|
4月前
|
小程序 前端开发 JavaScript
微信小程序MINA框架
【6月更文挑战第4天】微信小程序MINA框架是一个专为小程序设计的框架,它主要分为两大部分:页面视图层(View)和AppService应用逻辑层。下面我将结合代码和图示来详细讲解MINA框架。
47 0
|
2月前
|
缓存 开发框架 JavaScript
人人都能看懂的鸿蒙 “JS 小程序” 数据绑定原理 | 解读鸿蒙源码
人人都能看懂的鸿蒙 “JS 小程序” 数据绑定原理 | 解读鸿蒙源码
|
3月前
|
移动开发 开发框架 前端开发
微信门户开发框架-使用指导说明书(2)--基于框架的开发过程
微信门户开发框架-使用指导说明书(2)--基于框架的开发过程
|
3月前
|
存储 开发框架 小程序
微信门户开发框架-使用指导说明书
微信门户开发框架-使用指导说明书
|
3月前
|
开发框架 前端开发 JavaScript
在微信框架模块中,基于Vue&Element前端的事件和内容的管理
在微信框架模块中,基于Vue&Element前端的事件和内容的管理
|
3月前
|
开发框架 移动开发 前端开发
在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
|
3月前
|
开发框架 前端开发 JavaScript
在微信框架模块中,基于Vue&Element前端,通过动态构建投票选项,实现单选、复选的投票操作
在微信框架模块中,基于Vue&Element前端,通过动态构建投票选项,实现单选、复选的投票操作
|
4月前
|
小程序 开发者 Windows
轻量、可靠的小程序 UI 框架 -- Vant Weapp的安装和使用
轻量、可靠的小程序 UI 框架 -- Vant Weapp的安装和使用
104 1