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,一经查实,立即删除!

相关文章

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…

简单仿百度自动搜索框

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;女票突然脑子一热要给…

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日在浦东足球场举行。作为当下最火热的电竞赛…

服务器2003蓝屏A5修复,0x000000a5蓝屏完美解决方法 Win7

0x000000a5蓝屏怎么办&#xff1f;在Win7系统下遭遇开机时出现蓝屏代码0x000000a5非常多&#xff0c;由于根据代码我们并不能分析出故障原因&#xff0c;那么只能求助于网络上大家分享的经验了&#xff0c;下面小编给大家分享下0x000000a5蓝屏完美解决方法&#xff0c;适用于Wi…

能设值多个rowkey吗_顶楼送了露台,悄悄搭建阳光房,偏偏我家露台多个帽子,能拆吗?...

你们有没有发现现在楼顶上都会有几个这样的“帽子"&#xff0c;呼呼直转&#xff0c;但大多数人并不是很明白这个东西是干嘛用的。昨天有网友私信&#xff1a;小区顶楼露台上这个不锈钢帽子可以加高吗&#xff1f;感觉每次去楼顶都会闻到一股油烟味。业主表示自己是住在顶…

金山云服务器内网带宽,金山云-文档中心-配置弹性网卡

本文为您介绍绑定弹性网卡后虚机中如何配置弹性网卡。挂载辅网卡后&#xff0c;Windows操作系统主机即可正常使用&#xff0c;不需要额外操作。如果实例使用以下几种镜像&#xff0c;则可以跳过网卡手动配置&#xff0c;直接进行路由配置。CentOS 7.6 64位CentOS 7.7 64位CentO…

Atom飞行手册翻译: 2.7 ~ 2.10

自动补全 如果你仍旧希望节约一些打字时间&#xff0c;Atom自带简单的自动补全功能。 通过使用ctrl-space&#xff0c;自动补全工具可以让你看到并插入可选的完整单词。 通常&#xff0c;自动补全工具会浏览当前打开的整个文档&#xff0c;寻找匹配你开始打出来的单词。 如果你…

Flash游戏开发实战(一)

我不得不承认&#xff0c;我不是植物大战僵尸的骨灰玩家&#xff0c;所以&#xff0c;如果你发现这个系列的教程有什么地方错误的&#xff0c;请告诉我。 定义游戏的主要结构 植物大战僵给了我们很好的视觉和感觉上的享受。你得保护你的房子以免被吃脑的僵尸入侵&#xff0c;这…

如何做相册_手机里的照片太多,不得已只能删除?那就试试制作电子相册吧

许多人都喜欢使用手机拍照&#xff0c;这也就导致自己手机相册中保存的照片越来越多&#xff0c;到后面想要再拍照片的话&#xff0c;就不得不删除之前的照片&#xff0c;如何解决这个办法呢&#xff1f;那就试试用【迅捷视频转换器】将其制作成电子相册吧。制作电子相册打开软…

python windows窗口置顶_想用Python编程却不知如何下手?一篇搞定编程准备工作

导读&#xff1a;为了简化Python编程的学习难度&#xff0c;开发过程中的工具、环境尽量使用同一套&#xff0c;此后所有的编程环境都以Windows系统下Python3.8为准&#xff0c;代码编辑器及IDE(集成开发环境)使用VS Code。目的很简单&#xff0c;就是为了让所有的讲解和学习集…

小程序 ajax 加载,小程序实战-小程序网络请求异步加载

最初看到小程序的网络请求的时候,尤其是演示示例中&#xff0c;userInfoReadyCallback这个函数更是一头雾水。其实并不怎么理解.一直很费解.网上各路大侠都有解释&#xff0c;但是就是&#xff0c;不知道是怎么个顺序,而我也是个对程序执行流程很关注的人,现在把我的心得分享给…

华为手机怎么强制关机_华为忘记锁屏密码怎么办?多品牌手机通用解锁密码

手机忘记登陆密码怎么办&#xff1f;下面小编介绍几种方法&#xff0c;轻松解开那些忘记锁屏密码的手机&#xff0c;第一种方法可能大家还知道&#xff0c;但第二种方法肯定没几个人知道。1、手机原地复活这种方法是可以解决锁屏密码的问题&#xff0c;但也一并把手机上所有的数…

HTML5与搜索引擎优化[转载]

原文&#xff1a;http://lusongsong.com/reed/398.html 我觉得HTML5的兴起完全是因为iPhone和iPad&#xff0c;自从Adobe停止开发flash、Android4.0不支持flash后&#xff0c;我觉得在不久的将来HTML5会广泛应用&#xff0c;而且HTML4已经10年没更新了。 HTML5与HTML4代码结构对…

如何让 zend studio 10 识别 Phalcon语法并且进行语法提示

让 zend studio 10 识别 Phalcon语法并且进行语法提示https://github.com/rogerthomas84/PhalconPHPDoc下载解压后&#xff0c;把里面 phalcon 整个目录复制到 workspace 的C:\Documents and Settings\Administrator\Zend\workspaces\DefaultWorkspace\.metadata\.plugins\org.…