baidumap vue 判断范围_vue 数据渲染

本文转载于 SegmentFault 社区社区专栏:山外de楼作者:山外de楼


  

前言

 vue 是如何将编译器中的代码转换为页面真实元素的?这个过程涉及到模板编译成 AST 语法树,AST 语法树构建渲染函数,渲染函数生成虚拟 dom,虚拟 dom 编译成真实 dom 这四个过程。本文着重分析后两个过程。
 

整体流程

解读代码之前,先看一张 vue 编译和渲染的整体流程图:7bcb7a46dcd51025d3f6974d6bcc0584.pngvue 会把用户写的代码中的 标签中的代码解析成 AST 语法树,再将处理后的 AST 生成相应的 render 函数,render 函数执行后会得到与模板代码对应的虚拟 dom,最后通过虚拟 dom 中新旧 vnode 节点的对比和更新,渲染得到最终的真实 dom。有了这个整体的概念我们再来结合源码分析具体的数据渲染过程。
  

从 vm.$mount 开始

vue 中是通过 mount 实例方法去挂载 vm 的,数据渲染的过程就发生在vm.mount 阶段。在这个方法中,最终会调用 mountComponent方法来完成数据的渲染。我们结合源码看一下其中的几行关键代码:
  
 updateComponent = () => {
vm._update(vm._render(), hydrating) // 生成虚拟dom,并更新真实dom
}
这是在 mountComponent 方法的内部,会定义一个 updateComponent方法,在这个方法中 vue 会通过 vm._render()函数生成虚拟 dom,并将生成的 vnode 作为第一个参数传入 vm._update()函数中进而完成虚拟 dom 到真实 dom 的渲染。第二个参数 hydrating是跟服务端渲染相关的,在浏览器中不需要关心。这个函数最后会作为参数传入到 vue 的 watch 实例中作为 getter函数,用于在数据更新时触发依赖收集,完成数据响应式的实现。这个过程不在本文的介绍范围内,在这里只要明白,当后续 vue 中的 data 数据变化时,都会触发 updateComponent 方法,完成页面数据的渲染更新。具体的关键代码如下:
  new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 触发beforeUpdate钩子
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false

// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
// 触发mounted钩子
callHook(vm, 'mounted')
}
return vm
}
代码中还有一点需要注意的是,在代码结束处,会做一个判断,当 vm 挂载成功后,会调用 vue 的 mounted 生命周期钩子函数。这也就是为什么我们在 mounted 钩子中执行代码时,vm 已经挂载完成的原因。
 

vm._render()

接下来具体分析 vue 生成虚拟 dom 的过程。前面说了这一过程是调用 vm._render()方法来完成的,该方法的核心逻辑是调用 vm.$createElement 方法生成 vnode,代码如下:
vnode = render.call(vm._renderProxy, vm.$createElement)
其中 vm.renderProxy是个代理,代理 vm,做一些错误处理,vm.$createElement是创建 vnode 的真正方法,该方法的定义如下:
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
可见最终调用的是 createElement方法来实现生成 vnode 的逻辑。在进一步介绍 createElement 方法之前,我们先理清楚两个个关键点:1. render的函数来源2. vnode到底是什么

render 方法的来源

在 vue 内部其实定义了两种 render 方法的来源,一种是如果用户手写了 render 方法,那么 vue 会调用这个用户自己写的 render 方法,即下面代码中的 vm.$createElement;另外一种是用户没有手写 render 方法,那么vue内部会把 template 编译成 render 方法,即下面代码中的 vm._c。不过这两个 render 方法最终都会调用 createElement 方法来生成虚拟 dom。
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
 vnode 类
vnode 就是用一个原生的 js 对象去描述 dom 节点的类。因为浏览器操作 dom 的成本是很高的,所以利用 vnode 生成虚拟 dom 比创建一个真实 dom 的代价要小很多。vnode 类的定义如下:
export default class VNode {
tag: string | void; // 当前节点的标签名
data: VNodeData | void; // 当前节点对应的对象
children: ?Array; // 当前节点的子节点
text: string | void; // 当前节点的文本
elm: Node | void; // 当前虚拟节点对应的真实dom节点
..../*创建一个空VNode节点*/export const createEmptyVNode = (text: string = '') => {const node = new VNode()
node.text = text
node.isComment = truereturn node
}/*创建一个文本节点*/export function createTextVNode (val: string | number) {return new VNode(undefined, undefined, undefined, String(val))
}
....
可以看到 vnode 类中仿照真实 dom 定义了很多节点属性和一系列生成各类节点的方法。通过对这些属性和方法的操作来达到模仿真实 dom 变化的目的。

createElement

有了前面两点的知识储备,接下来回到 createElement 生成虚拟 dom 的分析。createElement 方法中的代码很多,这里只介绍跟生成虚拟 dom 相关的代码。该方法总体来说就是创建并返回一个 vnode 节点。在这个过程中可以拆分成三件事情:1. 子节点的规范化处理;2. 根据不同的情形创建不同的 vnode 节点类型;3. vnode 创建后的处理。下面开始分析这 3 个步骤:
子节点的规范化处理
  if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
为什么会有这个过程,是因为传入的参数中的子节点是 any 类型,而 vue 最终生成的虚拟 dom 实际上是一个树状结构,每一个 vnode 可能会有若干个子节点,这些子节点应该也是 vnode 类型。所以需要对子节点处理,将子节点统一处理成一个 vnode 类型的数组。同时还需要根据 render 函数的来源不同,对子节点的数据结构进行相应处理。
创建 vnode 节点
这部分逻辑是对 tag 标签在不同情况下的处理,梳理一下具体的判断case如下:1. 如果传入的 tag 标签是字符串,则进一步进入下列第 2 点和第 3 点判断,如果不是字符串则创建一个组件类型 vnode 节点。2. 如果是内置的标签,则创建一个相应的内置标签 vnode 节点。3. 如果是一个组件标签,则创建一个组件类型 vnode 节点。4. 其他情况下,则创建一个命名空间未定义的 vnode 节点。
  let vnode, ns
if (typeof tag === 'string') {
let Ctor
// 获取tag的名字空间
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)

// 判断是否是内置的标签,如果是内置的标签则创建一个相应节点
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on .`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// 如果是组件,则创建一个组件类型节点
// 从vm实例的option的components中寻找该tag,存在则就是一个组件,创建相应节点,Ctor为组件的构造类
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children

//其他情况,在运行时检查,因为父组件可能在序列化子组件的时候分配一个名字空间
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
// tag不是字符串的时候则是组件的构造类,创建一个组件节点
vnode = createComponent(tag, data, context, children)
}
vnode 创建后的处理这部分同样也是一些 if/else 分情况的处理逻辑:1. 如果 vnode 成功创建,且是一个数组类型,则返回创建好的 vnode 节点2. 如果 vnode 成功创建,且有命名空间,则递归所有子节点应用该命名空间3. 如果 vnode 没有成功创建则创建并返回一个空的 vnode 节点
   
 if (Array.isArray(vnode)) {
// 如果vnode成功创建,且是一个数组类型,则返回创建好的vnode节点
return vnode
} else if (isDef(vnode)) {
// 如果vnode成功创建,且名字空间,则递归所有子节点应用该名字空间
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
// 如果vnode没有成功创建则创建空节点
return createEmptyVNode()
}

vm._update()

vm._update()做的事情就是把 vm._render()生成的虚拟 dom 渲染成真实 dom。_update()方法内部会调用 vm.__patch__ 方法来完成视图更新,最终调用的是 createPatchFunction方法,该方法的代码量和逻辑都非常多,它定义在 src/core/vdom/patch.js文件中。下面介绍下具体的 patch 流程和流程中用到的重点方法:

重点方法

1. createElm:该方法会根据传入的虚拟 dom 节点创建真实的 dom 并插入到它的父节点中2. sameVnode:判断新旧节点是否是同一节点。3. patchVnode:当新旧节点是相同节点时,调用该方法直接修改节点,在这个过程中,会利用 diff 算法,循环进行子节点的的比较,进而进行相应的节点复用或者替换。4. updateChildren方法:diff 算法的具体实现过程
 

patch 流程

第一步:
判断旧节点是否存在,如果不存在就调用 createElm() 创建一个新的 dom 节点,否则进入第二步判断。
 
 if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
}
第二步:
通过 sameVnode() 判断新旧节点是否是同一节点,如果是同一个节点则调用 patchVnode() 直接修改现有的节点,否则进入第三步判断。
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
/*是同一个节点的时候直接修改现有的节点*/
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
第三步:
如果新旧节点不是同一节点,则调用 createElm()创建新的 dom,并更新父节点的占位符,同时移除旧节点。
else {
....
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
/*更新父的占位符节点*/
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor) /*调用destroy回调*/
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor) /*调用create回调*/
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0) /* 删除旧节点 */
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode) /* 调用destroy钩子 */
}
}
第四步:返回 vnode.elm,即最后生成的虚拟 dom 对应的真实 dom,将 vm.$el 赋值为这个 dom 节点,完成挂载。其中重点的过程在第二步和第三步中,特别是 diff 算法对新旧节点的比较和更新很有意思。

其他注意点

sameVnode 的实际应用

在 patch 的过程中,如果两个节点被判断为同一节点,会进行复用。这里的判断标准是:1. key 相同2. tag(当前节点的标签名)相同3. isComment(是否为注释节点)相同4. data 的属性相同平时写 vue 时会遇到一个组件中用到了 A 和 B 两个相同的子组件,可以来回切换。有时候会出现改变了 A 组件中的值,切到 B 组件中,发现 B 组件的值也被改变成和 A 组件一样了。这就是因为 vue 在 patch 的过程中,判断出了 A 和 B 是 sameVnode,直接进行复用引起的。根据源码的解读,可以很容易地解决这个问题,就是给 A 和 B 组件分别加上不同的 key 值,避免 A 和 B 被判断为同一组件。

虚拟 DOM 如何映射到真实的 DOM 节点

vue 为平台做了一层适配层,浏览器平台的代码在 /platforms/web/runtime/node-ops.js。不同平台之间通过适配层对外提供相同的接口,虚拟 dom 映射转换真实 dom 节点的时候,只需要调用这些适配层的接口即可,不需要关心内部的实现。最后通过上述的源码和实例的分析,我们完成了 Vue 中数据渲染的完整解读。
- END -273ecd523f886fa538ae6cf80199fb10.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459951.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring+websocket综合(springMVC+spring+MyBatis这是SSM框架和websocket集成技术)

java-websocket该建筑是easy。儿童无用的框架可以在这里下载主线和个人教学好java-websocket计划&#xff1a; Apach Tomcat 8.0.3MyEclipsemavenJDK1.7&#xff1a; http://download.csdn.net/detail/up19910522/7719087 spring4.0以后增加了对websocket技术的支持&#xff0c…

电子计算机可直接执行的指令机器内部是以,电子计算机可直接执行的指令在机器内部是以( )表示....

问题&#xff1a;电子计算机可直接执行的指令在机器内部是以( )表示.更多相关问题实际GDP衡量的是在特定年度内生产的最终产品与服务的价值&#xff0c;使用________。A&#xff0e;基年价格B&#xff0e;当年价格C&#xff0e;岗位评价结果的形式多种多样&#xff0c;但最值得…

Audit(查看审核/审计信息)

2013需要到 网站集管理-网站集审核设置 中设置要开启的审核项&#xff0c;开启之后sharepoint才会记录信息&#xff0c;信息存储在contentDB的AuditData中&#xff1b; 去读审计信息的sharepoint代码如下&#xff1a; 1 using (SPSite site new SPSite(SiteUrl))2 {3 usin…

jtoken判断是否包含键_Redis 数据库、键过期的实现

今天看看作为内存数据库&#xff0c;Redis 是怎么存储数据的以及键是怎么过期的。阅读这篇文章你将会了解到&#xff1a;Redis 的数据库实现Redis 键过期的策略数据库的实现我们先看代码 server.h/redisServerstruct redisServer{ ... //保存 db 的数组 redisDb *db; //db 的数…

JBoss配置详解

为什么80%的码农都做不了架构师&#xff1f;>>> 2.0.1 JBOSS 的一点说明 $JBOSS-HOME/server/下有3个目录&#xff0c;all/default/minimal&#xff0c;它们是表示3种配置&#xff0c;全部的配置、默认配置、最小配置&#xff0c;我们在启动JBOSS服务时&#xff0c…

计算机等级考试2010,2010年全国计算机等级考试相关政策

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼二级QBASIC 21 120 60二级FORTRAN 22 120 60 不接受新考生报名二级C 24 120 60二级FOXBASE 25 120 60二级VB 26 90 90二级VFP 27 90 90三级PC技术 33 120 60三级信息管理技术 34 120 60三级网络技术 35 120 60三级数据库技术 36 12…

ACE(Adaptive Communication Environment)介绍

转自 http://blog.csdn.net/huangyong19870618/article/details/4954512 Adaptive Communication Environment&#xff08;自适配通信环境&#xff09;&#xff0c;简称ACE。为一个以C的Template技术所做成的开放源代码的可跨平台的网络应用程序的程序库套件。 相关书籍   T…

rpn产生proposals_一文读懂RPN和ROI Align

rpn和roi align是two-stage detector中比较关键的两个操作&#xff0c;这两个操作将two-stage detector中的两个stage连接起来&#xff0c;变成end-to-end(端到端)的网络&#xff0c;同时也给整个检测方法的性能带来提升。rpn为roi align提供高质量的候选框&#xff0c;即propo…

多个相同参数表单提交

前情提要&#xff1a; 现在需要使用异步提交表单数据&#xff08;QueryString方式&#xff09;&#xff0c;但数据个数比较多&#xff0c;一个个拼接比较麻烦&#xff0c;这时可以使用jQuery的方法$("form").serialize()对表单进行序列化。但是如果这时&#xff0c…

简单仿百度自动搜索框

body中 <body> 请输入搜索内容:<input type"text" name"name" value"" id"txt" style"border:1px solid gray;margin:0;padding:0; width:200px;" /></body> View Code<script>中 <script type&…

layui上传文件请求接口异常_SpringMVC实现文件上传与下载,拦截器,异常处理

第一章&#xff1a;响应数据和结果视图1. 返回字符串Controller方法返回字符串可以指定逻辑视图的名称&#xff0c;根据视图解析器为物理视图的地址。RequestMapping(value"/hello")public String sayHello() {System.out.println("Hello SpringMVC!!");//…

球星测试软件,2KOL球星测评丨篮球之神,迈克尔.乔丹(96版)

迈克尔.乔丹(96版)迈克尔乔丹在在1984年NBA选秀中于第1轮第3位被芝加哥公牛队选中。(他的前面两位分别是哈基姆奥拉朱旺和萨姆鲍伊)91-93赛季&#xff0c;乔丹连续2次荣膺常规赛MVP和3次总决赛FMVP &#xff0c;并率领芝加哥公牛队首夺3连冠。 93年10月6日因父亲被害而宣布退役…

iphone无线充电充电测试软件,瞎折腾星人的测评 篇一:想体验iPhone的无线充电?这可能是最具性价比的选择了!...

瞎折腾星人的测评 篇一&#xff1a;想体验iPhone的无线充电&#xff1f;这可能是最具性价比的选择了&#xff01;2018-11-28 00:26:032点赞2收藏0评论最近恰逢更换新手机&#x1f4f1;iPhone XS Max的重大活动&#xff0c;功臣iPhone 7退居二线&#xff0c;女票突然脑子一热要给…

Gson读写JSON 数据

一、创建实体&#xff1b; package com.bo.entity;public class ShiTiInFo {private String shitia; //试题名称private String shitib; //大题名称private String shitic; //小题名称private String shitid; //试题题号private int shitie; //试题小号public String getShitia…

poj 2431

大意&#xff1a; 有n个加油点&#xff0c;给出每个加油点距离终点的位置和能加多少油&#xff0c;最后一行给出总长度和最初的油量。求最少加几次油能到终点&#xff0c;不能到的话输出-1. Sample Input 4 4 4 5 2 11 5 15 10 25 10Sample Output 2分析&#xff1a; 一开始打算…

cts游戏手机版_cts游戏

cts游戏是一款非常逼真的驾驶模拟游戏&#xff0c;它以经典的驾驶和运输为主题&#xff0c;通往不同城市拥有不同的运输路线玩家们可以在驾驶过程中欣赏风景&#xff0c;感受全新的天气变化&#xff0c;当天气不好会影响驾驶&#xff0c;玩家需要小心驾驶。确保货物安全发送到指…

服务器系统server 2008,windows server 2008 R2 操作系统

您好&#xff0c;您的问题解决了吗&#xff1f;如果回复对您有所帮助&#xff0c;请记住将其标记为答案。如果否&#xff0c;请回复并告诉我们当前情况&#xff0c;以便提供进一步的帮助。Best regard,SylviaPlease remember to mark the replies as answers if they help. &qu…

MVC5+EF6 入门完整教程四

MVC5EF6 入门完整教程四 原文:MVC5EF6 入门完整教程四上篇文章主要讲了如何配置EF, 我们回顾下主要过程&#xff1a; 创建Data Model 创建Database Context 创建databaseInitializer配置entityFramework的context配置节。 对这个过程还有疑问的可以去上篇再看一下。 本次我…

android js调试

http://blog.allenm.me/ 其他平台去这篇文章看 1 //js调试调试功能支持4.4版本以上的2 if(Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {3 WebView.setWebContentsDebuggingEnabled(true);4 }5 //j…

python 比赛成绩预测_利用 Python 预测英雄联盟胜负,分析了 5 万多场比赛才得出的数据!值得,涨知识了!...

Mika 来源 | 头图 |CSDN自东方IC今天教大家用Python预测英雄联盟比赛胜负。Show me data&#xff0c;用数据说话今天我们聊一聊 Python预测LOL胜负目前&#xff0c;英雄联盟S10全球总决赛正在火热进行中&#xff0c;最终决赛于10月31日在浦东足球场举行。作为当下最火热的电竞赛…