当前位置: 首页 > 编程日记 > 正文

vue:虚拟dom的实现

Vitual DOM是一种虚拟dom技术,本质上是基于javascript实现的,相对于dom对象,javascript对象更简单,处理速度更快,dom树的结构,属性信息都可以很容易的用javascript对象来表示:

let element={tagName:'ul',//节点标签名props:{//dom的属性,用一个对象存储键值对id:'list'},children:[//该节点的子节点{tagName:'li',props:{class:'item'},children:['aa']},{tagName:'li',props:{class:'item'},children:['bb']},{tagName:'li',props:{class:'item'},children:['cc']}]
}
对应的html写法是:
<ul id='list'><li class='item'>aa</li><li class='item'>aa</li><li class='item'>aa</li>
</ul>

Virtual DOM并没有完全实现DOMVirtual DOM最主要的还是保留了Element之间的层次关系和一些基本属性. 你给我一个数据,我根据这个数据生成一个全新的Virtual DOM,然后跟我上一次生成的Virtual DOMdiff,得到一个Patch,然后把这个Patch打到浏览器的DOM上去。

我们可以通过javascript对象表示的树结构来构建一棵真正的dom树,当数据状态发生变化时,可以直接修改这个javascript对象,接着对比修改后的javascript对象,记录下需要对页面做的dom操作,然后将其应用到真正的dom树,实现视图的更新,这个过程就是Virtual DOM的核心思想。

VNode的数据结构图:
clipboard.png

clipboard.png

VNode生成最关键的点是通过render2种生成方式,第一种是直接在vue对象的option中添加render字段。第二种是写一个模板或指定一个el根元素,它会首先转换成模板,经过html语法解析器生成一个ast抽象语法树,对语法树做优化,然后把语法树转换成代码片段,最后通过代码片段生成function添加到optionrender字段中。

ast语法优的过程,主要做了2件事:

  • 会检测出静态的class名和attributes,这样它们在初始化渲染后就永远不会再被比对了。
  • 会检测出最大的静态子树(不需要动态性的子树)并且从渲染函数中萃取出来。这样在每次重渲染时,它就会直接重用完全相同的vnode,同时跳过比对。
src/core/vdom/create-element.jsconst SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {// 兼容不传data的情况if (Array.isArray(data) || isPrimitive(data)) {normalizationType = childrenchildren = datadata = undefined}// 如果alwaysNormalize是true// 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE// 调用_createElement创建虚拟节点return _createElement(context, tag, data, children, normalizationType)
}function _createElement (context, tag, data, children, normalizationType) {/*** 如果存在data.__ob__,说明data是被Observer观察的数据* 不能用作虚拟节点的data* 需要抛出警告,并返回一个空节点* 被监控的data不能被用作vnode渲染的数据的原因是:* data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作*/if (data && data.__ob__) {process.env.NODE_ENV !== 'production' && warn(`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +'Always create fresh vnode data objects in each render!',context)return createEmptyVNode()}// 当组件的is属性被设置为一个falsy的值// Vue将不会知道要把这个组件渲染成什么// 所以渲染一个空节点if (!tag) {return createEmptyVNode()}// 作用域插槽if (Array.isArray(children) &&typeof children[0] === 'function') {data = data || {}data.scopedSlots = { default: children[0] }children.length = 0}// 根据normalizationType的值,选择不同的处理方法if (normalizationType === ALWAYS_NORMALIZE) {children = normalizeChildren(children)} else if (normalizationType === SIMPLE_NORMALIZE) {children = simpleNormalizeChildren(children)}let vnode, ns// 如果标签名是字符串类型if (typeof tag === 'string') {let Ctor// 获取标签名的命名空间ns = config.getTagNamespace(tag)// 判断是否为保留标签if (config.isReservedTag(tag)) {// 如果是保留标签,就创建一个这样的vnodevnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)// 如果不是保留标签,那么我们将尝试从vm的components上查找是否有这个标签的定义} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {// 如果找到了这个标签的定义,就以此创建虚拟组件节点vnode = createComponent(Ctor, data, context, children, tag)} else {// 兜底方案,正常创建一个vnodevnode = new VNode(tag, data, children,undefined, undefined, context)}// 当tag不是字符串的时候,我们认为tag是组件的构造类// 所以直接创建} else {vnode = createComponent(tag, data, context, children)}// 如果有vnodeif (vnode) {// 如果有namespace,就应用下namespace,然后返回vnodeif (ns) applyNS(vnode, ns)return vnode// 否则,返回一个空节点} else {return createEmptyVNode()}
}

方法的功能是给一个Vnode对象对象添加若干个子Vnode,因为整个Virtual DOM是一种树状结构,每个节点都可能会有若干子节点。然后创建一个VNode对象,如果是一个reserved tag(比如html,head等一些合法的html标签)则会创建普通的DOM VNode,如果是一个component tag(通过vue注册的自定义component),则会创建Component VNode对象,它的VnodeComponentOptions不为Null.
创建好Vnode,下一步就是要把Virtual DOM渲染成真正的DOM,是通过patch来实现的,源码如下:

src/core/vdom/patch.jsreturn function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { // oldVnoe:dom||当前vnode,vnode:vnoder=对象类型,hydration是否直接用服务端渲染的dom元素if (isUndef(vnode)) {if (isDef(oldVnode)) invokeDestroyHook(oldVnode)return}let isInitialPatch = falseconst insertedVnodeQueue = []if (isUndef(oldVnode)) {// 空挂载(可能是组件),创建新的根元素。isInitialPatch = truecreateElm(vnode, insertedVnodeQueue, parentElm, refElm)} else {const isRealElement = isDef(oldVnode.nodeType)if (!isRealElement && sameVnode(oldVnode, vnode)) {// patch 现有的根节点patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)} else {if (isRealElement) {// 安装到一个真实的元素。// 检查这是否是服务器渲染的内容,如果我们可以执行。// 成功的水合作用。if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {oldVnode.removeAttribute(SSR_ATTR)hydrating = true}if (isTrue(hydrating)) {if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {invokeInsertHook(vnode, insertedVnodeQueue, true)return oldVnode} else if (process.env.NODE_ENV !== 'production') {warn('The client-side rendered virtual DOM tree is not matching ' +'server-rendered content. This is likely caused by incorrect ' +'HTML markup, for example nesting block-level elements inside ' +'<p>, or missing <tbody>. Bailing hydration and performing ' +'full client-side render.')}}// 不是服务器呈现,就是水化失败。创建一个空节点并替换它。oldVnode = emptyNodeAt(oldVnode)}// 替换现有的元素const oldElm = oldVnode.elmconst parentElm = nodeOps.parentNode(oldElm)// create new nodecreateElm(vnode,insertedVnodeQueue,// 极为罕见的边缘情况:如果旧元素在a中,则不要插入。// 离开过渡。只有结合过渡+时才会发生。// keep-alive + HOCs. (#4590)oldElm._leaveCb ? null : parentElm,nodeOps.nextSibling(oldElm))// 递归地更新父占位符节点元素。if (isDef(vnode.parent)) {let ancestor = vnode.parentconst patchable = isPatchable(vnode)while (ancestor) {for (let i = 0; i < cbs.destroy.length; ++i) {cbs.destroy[i](ancestor)}ancestor.elm = vnode.elmif (patchable) {for (let i = 0; i < cbs.create.length; ++i) {cbs.create[i](emptyNode, ancestor)}// #6513// 调用插入钩子,这些钩子可能已经被创建钩子合并了。// 例如使用“插入”钩子的指令。const insert = ancestor.data.hook.insertif (insert.merged) {// 从索引1开始,以避免重新调用组件挂起的钩子。for (let i = 1; i < insert.fns.length; i++) {insert.fns[i]()}}} else {registerRef(ancestor)}ancestor = ancestor.parent}}// destroy old nodeif (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0)} else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode)}}}invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)return vnode.elm}

patch支持的3个参数,其中oldVnode是一个真实的DOM或者一个VNode对象,它表示当前的VNode,vnodeVNode对象类型,它表示待替换的VNode,hydrationbool类型,它表示是否直接使用服务器端渲染的DOM元素,下面流程图表示patch的运行逻辑:

clipboard.png

patch运行逻辑看上去比较复杂,有2个方法createElmpatchVnode是生成dom的关键,源码如下:

/*** @param vnode根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createElm方法,* @param insertedVnodeQueue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法*/let inPre = 0function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {vnode.isRootInsert = !nested // 过渡进入检查if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {return}const data = vnode.dataconst children = vnode.childrenconst tag = vnode.tagif (isDef(tag)) {if (process.env.NODE_ENV !== 'production') {if (data && data.pre) {inPre++}if (!inPre &&!vnode.ns &&!(config.ignoredElements.length &&config.ignoredElements.some(ignore => {return isRegExp(ignore)? ignore.test(tag): ignore === tag})) &&config.isUnknownElement(tag)) {warn('Unknown custom element: <' + tag + '> - did you ' +'register the component correctly? For recursive components, ' +'make sure to provide the "name" option.',vnode.context)}}vnode.elm = vnode.ns? nodeOps.createElementNS(vnode.ns, tag): nodeOps.createElement(tag, vnode)setScope(vnode)/* istanbul ignore if */if (__WEEX__) {// in Weex, the default insertion order is parent-first.// List items can be optimized to use children-first insertion// with append="tree".const appendAsTree = isDef(data) && isTrue(data.appendAsTree)if (!appendAsTree) {if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}insert(parentElm, vnode.elm, refElm)}createChildren(vnode, children, insertedVnodeQueue)if (appendAsTree) {if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}insert(parentElm, vnode.elm, refElm)}} else {createChildren(vnode, children, insertedVnodeQueue)if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}insert(parentElm, vnode.elm, refElm)}if (process.env.NODE_ENV !== 'production' && data && data.pre) {inPre--}} else if (isTrue(vnode.isComment)) {vnode.elm = nodeOps.createComment(vnode.text)insert(parentElm, vnode.elm, refElm)} else {vnode.elm = nodeOps.createTextNode(vnode.text)insert(parentElm, vnode.elm, refElm)}}

方法会根据vnode的数据结构创建真实的DOM节点,如果vnodechildren,则会遍历这些子节点,递归调用createElm方法,InsertedVnodeQueue是记录子节点创建顺序的队列,每创建一个DOM元素就会往这个队列中插入当前的VNode,当整个VNode对象全部转换成为真实的DOM树时,会依次调用这个队列中的VNode hookinsert方法。

/*** 比较新旧vnode节点,根据不同的状态对dom做合理的更新操作(添加,移动,删除)整个过程还会依次调用prepatch,update,postpatch等钩子函数,在编译阶段生成的一些静态子树,在这个过程* @param oldVnode 中由于不会改变而直接跳过比对,动态子树在比较过程中比较核心的部分就是当新旧vnode同时存在children,通过updateChildren方法对子节点做更新,* @param vnode* @param insertedVnodeQueue* @param removeOnly*/function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {if (oldVnode === vnode) {return}const elm = vnode.elm = oldVnode.elmif (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue)} else {vnode.isAsyncPlaceholder = true}return}// 用于静态树的重用元素。// 注意,如果vnode是克隆的,我们只做这个。// 如果新节点不是克隆的,则表示呈现函数。// 由热重加载api重新设置,我们需要进行适当的重新渲染。if (isTrue(vnode.isStatic) &&isTrue(oldVnode.isStatic) &&vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance = oldVnode.componentInstancereturn}let iconst data = vnode.dataif (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {i(oldVnode, vnode)}const oldCh = oldVnode.childrenconst ch = vnode.childrenif (isDef(data) && isPatchable(vnode)) {for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)}if (isUndef(vnode.text)) {if (isDef(oldCh) && isDef(ch)) {if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)} else if (isDef(ch)) {if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)} else if (isDef(oldCh)) {removeVnodes(elm, oldCh, 0, oldCh.length - 1)} else if (isDef(oldVnode.text)) {nodeOps.setTextContent(elm, '')}} else if (oldVnode.text !== vnode.text) {nodeOps.setTextContent(elm, vnode.text)}if (isDef(data)) {if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)}}

updateChildren方法解析在此:vue:虚拟DOM的patch

相关文章:

【Ubuntu】apt-get命令小结

apt-get install 安装软件包 apt-get remove 删除已安装的软件包&#xff08;保留配置文件&#xff09; apt-get update 更新 apt-get autoremove 自动删除没用的包 apt-get purge 删除包&#xff0c;不保留配置文件 apt-get –purge remove

腾讯58篇论文入选CVPR 2019,两年增长超200%

全球计算机视觉顶级会议 IEEE CVPR 2019(Computer Vision and Pattern Recognition&#xff0c;即IEEE国际计算机视觉与模式识别会议) 即将于6月在美国长滩召开。本届大会总共录取来自全球论文1299篇。中国团队表现不俗&#xff0c;此次&#xff0c;腾讯公司有超过58篇论文被本…

SQL Server 2008备份策略设计下(六)

上一篇博文探讨了各种恢复模式和备份类型&#xff0c;这一节继续来探讨如何设计备份策略。设计一个数据库的最佳备份策略&#xff0c;会面临如何选择使用哪种恢复模式的问题&#xff0c;因为恢复模式控制着备份和还原的行为。一般来讲&#xff0c;简单恢复模式一般适合用于测试…

【Qt】Qt样式表(Style Sheet):官网说明及例子

网址 http://doc.qt.io/qt-5/stylesheet.html 样式表语法 各种Qt控件的样式表例子 Qt样式表参考手册

神爱程序员,于是带来Python

“我累了&#xff0c;需要很长时间的休息。”2018 年 7 月&#xff0c;在 PEP 572&#xff08;Python 改进提案&#xff09; 被接受后的第三天&#xff0c;由于仍然不断面对着别人的质疑&#xff0c;反馈意见不断袭来&#xff0c;让这位一手缔造新的编程语言帝国的图灵奖获得者…

Dubbo配置文件详解

为新项目练手&#xff0c;把项目中用到的web service、RMI的服务改用DubboZookeeperSpring&#xff0c;网上找到几篇不错的配置详解 1.此篇博文主要从以下几种配置方式来讲 XML 配置文件方式、XML 配置文件方式、annotation 配置方式 https://www.cnblogs.com/chanshuyi/p/514…

Mono源代码学习笔记:Console类(四)

NullStream 类 (internal class) 下面就是 mcs/class/corlib/System.IO/NullStream.cs&#xff1a; 01: namespace System.IO 02: { 03: class NullStream : Stream 04: { 05: public override bool CanRead { get { return true; } } 06: public override b…

Java帝国对Python的渗透能成功吗?

作者 | 刘欣转载自码农翻身&#xff08;公众号 ID&#xff1a;coderising&#xff09;引子Java 帝国已经成立 20 多年&#xff0c;经过历代国王的励精图治&#xff0c;可以说是地大物博&#xff0c;码农众多。 可是国王依然不满足&#xff0c;整天想着如何继续开拓疆土&#xf…

【杂】突然有个想法,为了防止公司或其他,监视你的qq或微信,可以做个程序,将信息打乱,分别用qq和微信传输,然后,再还原

突然有个想法&#xff0c;为了防止公司或其他&#xff0c;监视你的qq或微信&#xff0c;可以做个程序&#xff0c;将信息打乱&#xff0c;分别用qq和微信传输&#xff0c;然后&#xff0c;再还原。

CTO 基本功大盘点 —— 没有这些技能,谈何远大前程?

本文由 「TGO鲲鹏会」原创&#xff0c;原文链接&#xff1a;CTO 基本功大盘点 —— 没有这些技能&#xff0c;谈何远大前程&#xff1f; 作者&#xff5c;刘海星 2018 年马上就要过去六分之一了&#xff0c;你的 KPI 完成多少了&#xff1f; 别沮丧&#xff0c;其实我想说的是&…

Windows Phone 7 不温不火学习之《创建用户控件》

同样出自微软的产品&#xff0c;像ASP.NET 一样&#xff0c;Windows Phone 7 也有一个叫UserControl 的东西。这个相当于一个组件&#xff0c;类似于Android 继承View 。 本篇将实现一个用户控件&#xff0c;默认为它添加高宽&#xff0c;并为它添加一个自己的事件&#xff0c;…

从起源、变体到评价指标,一文解读NLP的注意力机制

作者 | yuquanle转载自AI小白入门&#xff08;ID:StudyForAI&#xff09;目录1.写在前面2.Seq2Seq 模型3.NLP中注意力机制起源4.NLP中的注意力机制 5.Hierarchical Attention6.Self-Attention7.Memory-based Attention 8.Soft/Hard Attention9.Global/Local Attention10.评价指…

【Git】ubuntu上git commit提交后如何保存和退出类似vim的界面,回到命令行

问题 使用 git commit 命令后&#xff0c;进入类似vim的界面&#xff0c;开始时&#xff0c;不知道如何保存&#xff0c;甚至不知道怎么退出该界面。 解决方法 1、使用 git commit 命令后&#xff0c;进入的是nano文本编辑器&#xff08;类似vim&#xff09;&#xff1b; 2…

linux硬盘满了问题排查

关键指令&#xff1a; df du find step1&#xff1a; 如果发现硬盘满了&#xff0c;首先要确定一下&#xff0c;使用df查看硬盘使用情况 df -h step2&#xff1a; 从第一步结果判定满了&#xff0c;确定哪些文件或哪个文件占了大头&#xff0c;使用du指令做逐步排查&#xff0c…

win2003登陸及關機設定

開啟未登陸可以關機鍵關機﹕ 到控制面板&#xff0c;本地安全策略&#xff0c;安全性選項﹐启用允许在未登录前关机 關關機事件跟踪﹕ 运行“gpedit.msc”命令打开组策略编辑器&#xff0c;依次展开“计算机配置”→“管理模板”→“系统”&#xff0c;将“顯示关闭事件跟踪程序…

【Qt】信号和槽对值传递参数和引用传递参数的总结

在同一个线程中 当信号和槽都在同一个线程中时&#xff0c;值传递参数和引用传递参数有区别&#xff1a; 值传递会复制对象&#xff1b;&#xff08;测试时&#xff0c;打印传递前后的地址不同&#xff09; 引用传递不会复制对象&#xff1b;&#xff08;测试时&#xff0c;…

Node.js入门(含NVM、NPM、NVM的安装)

本文最初发表于博客园&#xff0c;并在GitHub上持续更新前端的系列文章。欢迎在GitHub上关注我&#xff0c;一起入门和进阶前端。 以下是正文。 Node.js的介绍 引擎 引擎的特性&#xff1a; JS的内核即引擎。因为引擎有以下特性&#xff1a; &#xff08;1&#xff09;转化的作…

GitHub日收7000星,Windows计算器项目开源即爆红!

说起此番微软开源 Windows 计算器&#xff0c;有道是“春风得意马蹄疾&#xff0c;一日‘摘星’ 7000”……整理 | 仲培艺来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;微软又来给自己拥抱开源的决心送”证明素材“了&#xff01;昨日&#xff0c;微软官宣在 MIT…

域环境下的***

首先还是先简要看一下域的概念吧&#xff1a; 域 (Domain) 是Windows网络中独立运行的单位&#xff0c;域之间相互访问则需要建立信任关系(即Trust Relation)。信任关系是连接在域与域之间的桥梁。当一个域与其他域建立了信任关系后&#xff0c;2个域之间不但可以按需要相互进行…

如何重构“箭头型”代码

本文主要起因是&#xff0c;一次在微博上和朋友关于嵌套好几层的if-else语句的代码重构的讨论&#xff08;微博原文&#xff09;&#xff0c;在微博上大家有各式各样的问题和想法。按道理来说这些都是编程的基本功&#xff0c;似乎不太值得写一篇文章&#xff0c;不过我觉得很多…

让数百万台手机训练同一个模型?Google把这套框架开源了

作者 | 琥珀出品 | AI科技大本营&#xff08;公众号id&#xff1a;rgznai100&#xff09;【导语】据了解&#xff0c;全球有 30 亿台智能手机和 70 亿台边缘设备。每天&#xff0c;这些电话与设备之间的交互不断产生新的数据。传统的数据分析和机器学习模式&#xff0c;都需要在…

【OpenCV】cv::VideoCapture 多线程测试

cv::VideoCapture多线程测试结果&#xff1a; 在多线程中使用抓取摄像头视频帧时线程安全的&#xff0c;但是&#xff0c;多个线程会共用摄像头的总帧率。 比如&#xff0c;我用两个线程测试30帧的摄像头&#xff0c;每个线程差多都是15帧。

都有Python了,还要什么编译器!

编译的目的是将源码转化为机器可识别的可执行程序&#xff0c;在早期&#xff0c;每次编译都需要重新构建所有东西&#xff0c;后来人们意识到可以让编译器自动完成一些工作&#xff0c;从而提升编译效率。但“编译器不过是用于代码生成的软机器&#xff0c;你可以使用你想要的…

【Qt】Qt发布程序时,报错: could not find or load the Qt platform plugin xcb

问题描述 Qt程序在发布时&#xff0c;报错&#xff1a; This application failed to start because it could not find or load the Qt platform plugin “xcb” in “”. Reinstalling the application may fix this problem Aborted (core dumped) 原因 没有将libqxcb…

jsky使用小记

jsky是一款深度WEB应用安全评估工具&#xff0c;能轻松应对各种复杂的WEB应用&#xff0c;全面深入发现里面存在的安全弱点。 jsky可以检测出包括SQL注入、跨站脚本、目录泄露、网页木马等在内的所有的WEB应用层漏洞&#xff0c;渗透测试功能让您熟知漏洞危害。 打开——新建扫…

BSP场景管理方法简介

BSP&#xff08;Binary Space Partition,二叉空间分割&#xff09;方法&#xff0c;在大型3d游戏场景管理方面&#xff0c;可以认为是已经证明了的&#xff0c;最成熟的&#xff0c;最经得起考验的场景管理方法。诸如虚幻系列引擎&#xff08;Unreal 1,2,3&#xff09;&#xf…

【Qt】Qt样式表总结(一):选择器

官方资料 https://blog.csdn.net/u010168781/article/details/81868523 注释 qss文件中使用:/**/ 来注释 样式规则 样式表由样式规则序列组成。样式规则由选择器和声明组成。选择器指定受规则影响的部件;声明指定应在小部件上设置哪些属性。 如: QLabel { color: white;…

JVM-01:类的加载机制

本文从  纯洁的微笑的博客  转载 原地址&#xff1a;http://www.ityouknow.com/jvm.html 类的加载机制 1、什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中&#xff0c;将其放在运行时数据区的方法区内&#xff0c;然后在堆区创建一个java.lang.…

CVPR 2019 | 惊艳的SiamMask:开源快速同时进行目标跟踪与分割算法

作者 | 周强&#xff08;CV君&#xff09;来源 | 我爱计算机视觉&#xff08;公众号id&#xff1a;aicvml&#xff09;责编 | Jane上面这张Gif图演示了 SiamMask 的效果&#xff0c;只需要鼠标滑动选择目标的包围框&#xff0c;即可同时实现目标跟踪与分割。这种视频里目标的像…

看看Entity Framework 4生成的复杂的分页SQL语句

之前发现Entity Framework 4生成的COUNT查询语句问题&#xff0c;今天又发现它生成的分页SQL语句问题&#xff0c;而LINQ to SQL却不存在这个问题。 >>> 来看一看&#xff0c;瞧一瞧&#xff01; 上代码&#xff1a; 看生成的SQL语句&#xff1a; 1. Entity Framework…