• 售前

  • 售后

热门帖子
入门百科

vue 捏造DOM快速入门

[复制链接]
大头226 显示全部楼层 发表于 2021-10-26 13:00:20 |阅读模式 打印 上一主题 下一主题
目次


  • 假造 DOM

    • 什么是假造 dom

  • 假造 dom 的作用
  • vue 中的假造 dom
  • vNode

    • 什么是 vNode
    • vNode 的范例
    • patch
    • 新增节点
    • 删除节点
    • 更新节点
    • 新增节点 - 源码分析
    • 删除节点 - 源码分析


假造 DOM



什么是假造 dom


dom 是文档对象模子,以节点树的形式来体现文档。
假造 dom 不是真正意义上的 dom。而是一个 javascript 对象。
正常的 dom 节点在 html 中是如许体现:
  1. <div class='testId'>
  2.     <p>你好</p>
  3.     <p>欢迎光临</p>
  4. </div>
复制代码
而在假造 dom 中大概是如许:
  1. {
  2.     tag: 'div',
  3.     attributes:{
  4.         class: ['testId']
  5.     },
  6.     children:[
  7.         // p 元素
  8.         // p 元素
  9.     ]
  10. }
复制代码
我们可以将假造 dom 拆分成两部分进行明白:假造 + dom。
       
  • 假造: 体现假造 dom 不是真正意义上的 dom,而是一个 javascript 对象;   
  • dom: 体现假造 dom 能以类似节点树的形式体现文档。

假造 dom 的作用


现在主流的框架都是声明式利用 dom 的框架。我们只需要描述状态与 dom 之间的映射关系即可,状态到视图(真实的 dom)的转换,框架会帮我们做。
最粗暴的做法是将状态渲染成视图,每次更新状态,都重新更新整个视图。
这种做法的性能可想而知。比力好的想法是:状态改变,只更新与状态相干的 dom 节点。假造 dom 只是实现这个想法的其中一种方法而已。
详细做法:
       
  • 状态 -> 真实 dom(最初)   
  • 状态 -> 假造 dom -> 真实 dom(利用假造 dom)
状态改变,重新天生一份假造 dom,将上一份和这一份假造 dom 进行对比,找出需要更新的部分,更新真实 dom。

vue 中的假造 dom


真实的 dom 是由 节点(Node)组成,假造 dom 则是由假造节点(vNode)组成。
假造 dom 在 vue 中主要做两件事:
       
  • 提供与真实节点(Node)对应的假造节点(vNode)   
  • 将新的假造节点与旧的假造节点进行对比,找出需要差别,然后更新视图
  1. “虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼 —— vue 官网
复制代码
vNode



什么是 vNode


上文提到,vNode(假造节点)对应的是真实节点(Node)。
vNode 可以明白成节点描述对象。描述了如何创建真实的 dom 节点。
vue.js 中有一个 vNode 类。可以利用它创建差别范例的 vNode 实例,差别范例的 vNode 对应着差别范例的 dom 元素。代码如下:
  1. export default class VNode {
  2.    constructor (
  3.     tag?: string,
  4.     data?: VNodeData,
  5.     children?: ?Array<VNode>,
  6.     text?: string,
  7.     elm?: Node,
  8.     context?: Component,
  9.     componentOptions?: VNodeComponentOptions,
  10.     asyncFactory?: Function
  11.   ) {
  12.     this.tag = tag
  13.     this.data = data
  14.     this.children = children
  15.     this.text = text
  16.     this.elm = elm
  17.     this.ns = undefined
  18.     this.context = context
  19.     this.fnContext = undefined
  20.     this.fnOptions = undefined
  21.     this.fnScopeId = undefined
  22.     this.key = data && data.key
  23.     this.componentOptions = componentOptions
  24.     this.componentInstance = undefined
  25.     this.parent = undefined
  26.     this.raw = false
  27.     this.isStatic = false
  28.     this.isRootInsert = true
  29.     this.isComment = false
  30.     this.isCloned = false
  31.     this.isOnce = false
  32.     this.asyncFactory = asyncFactory
  33.     this.asyncMeta = undefined
  34.     this.isAsyncPlaceholder = false
  35.   }
  36.   get child (): Component | void {
  37.     return this.componentInstance
  38.   }
  39. }
复制代码
从代码不难看出 vNode 类创建的实例,本质上就是一个平凡的 javascript 对象。

vNode 的范例


前面我们已经介绍通过 vNode 类可以创建差别范例的 vNode。而差别范例的 vNode 是由有用属性区分。例如 isComment = true 体现解释节点;isCloned = true 体现克隆节点等等。
vNode 范例有:解释节点、文本节点、克隆节点、元素节点、组件节点。
以下是解释节点、文本节点和克隆节点的代码:
  1. /*
  2. 注释节点
  3. 有效属性:{isComment: true, text: '注释节点'}
  4. */
  5. export const createEmptyVNode = (text: string = '') => {
  6.   const node = new VNode()
  7.   node.text = text
  8.   // 注释
  9.   node.isComment = true
  10.   return node
  11. }
  12. /*
  13. 文本节点
  14. 有效属性:{text: '文本节点'}
  15. */
  16. export function createTextVNode (val: string | number) {
  17.   return new VNode(undefined, undefined, undefined, String(val))
  18. }
  19. // optimized shallow clone
  20. // used for static nodes and slot nodes because they may be reused across
  21. // 用于静态节点和插槽节点
  22. // multiple renders, cloning them avoids errors when DOM manipulations rely
  23. // on their elm reference.
  24. // 克隆节点
  25. export function cloneVNode (vnode: VNode): VNode {
  26.   const cloned = new VNode(
  27.     vnode.tag,
  28.     vnode.data,
  29.     // #7975
  30.     // clone children array to avoid mutating original in case of cloning
  31.     // a child.
  32.     vnode.children && vnode.children.slice(),
  33.     vnode.text,
  34.     vnode.elm,
  35.     vnode.context,
  36.     vnode.componentOptions,
  37.     vnode.asyncFactory
  38.   )
  39.   cloned.ns = vnode.ns
  40.   cloned.isStatic = vnode.isStatic
  41.   cloned.key = vnode.key
  42.   cloned.isComment = vnode.isComment
  43.   cloned.fnContext = vnode.fnContext
  44.   cloned.fnOptions = vnode.fnOptions
  45.   cloned.fnScopeId = vnode.fnScopeId
  46.   cloned.asyncMeta = vnode.asyncMeta
  47.   // 标记是克隆节点
  48.   cloned.isCloned = true
  49.   return cloned
  50. }
复制代码
克隆节点实在就是将现有节点的所有属性赋值到新节点中,最后用
  1. cloned.isCloned = true
复制代码
标志自身是克隆节点。
元素节点通常有以下 4 个属性:
       
  • tag:节点名称。例如 div、p   
  • data:节点上的数据。例如 class、style   
  • children:子节点   
  • context:在组件内出现
组件节点与元素节点类似,包含两个独有的属性:
       
  • componentOptions:组件节点的选项参数,例如propsData、listeners、children、tag   
  • componentInstance:组件的实例

patch


前面已经介绍了假造 dom 在 vue 中做的第一件事:提供与真实节点(Node)对应的假造节点(vNode);接下来介绍第二件事:将新的假造节点与旧的假造节点进行对比,找出需要差别,然后更新视图。
第二件事在 vue 中的实现叫做 patch,即打补丁、修补的意思。通过对比新旧 vNode,找出差别,然后在现有 dom 的底子上进行修补,从而实现视图更新。
对比 vNode 找差别是手段,更新视图才是目的。
而更新视图无非就是新增节点、删除节点和更新节点。接下来我们逐一分析什么时候新增节点、在哪里新增;什么时候删除节点,删除哪个;什么时候更新节点,更新哪个;
注:当 vNode 与 oldVNode 不雷同的时候,以 vNode 为准。

新增节点


一种环境是:vNode 存在而 oldVNode 不存在时,需要新增节点。最范例的是初次渲染,因为 odlVNode 是不存在的。
另一种环境是 vNode 与 oldVNode 完全不是同一个节点。这时就需要利用 vNode 天生真实的 dom 节点并插入到 oldVNode 指向的真实 dom 节点旁边。oldVNode 则是一个被废弃的节点。例如下面这种环境:
  1. <div>
  2.   <p v-if="type === 'A'">
  3.     我是节点A
  4.   </p>
  5.   <span v-else-if="type === 'B'">
  6.     我是与A完全不同的节点B
  7.   </span>
  8. </div>
复制代码
当 type 由 A 变为 B,节点就会从 p 变成 span,由于 vNode 与 oldVNode 完全不是同一个节点,所以需要新增节点。

删除节点


当节点只在 oldVNode 中存在时,直接将其删除即可。

更新节点


前面介绍了新增节点和删除节点的场景,发现它们有一个共同点:vNode 与 oldVNode 完全不雷同。
但更常见的场景是 vNode 与 oldVNode 是同一个节点。然后我们需要对它们(vNode 与 oldVNode)进行一个更细致的对比,再对 oldVNode 对应的真实节点进行更新。
对于文本节点,逻辑自然简朴。起首对比新旧 vNode,发现是同一个节点,然后将 oldVNode 对应的 dom 节点的文本改成 vNode 中的文本即可。但对于复杂的 vNode,比如界面中的一颗树组件,这个过程就会变得复杂。

新增节点 - 源码分析


思索一下:前面说到 vNode 的范例有:解释节点、文本节点、克隆节点、元素节点、组件节点。叨教这几种范例都会被创建并插入到 dom 中吗?
答:只有解释节点、文本节点、元素节点。因为 html 只熟悉这几种。
由于只有上面三种节点范例,根据范例做相应的创建,然后插入对应的位置即可。
以元素节点为例,假如 vNode 有 tag 属性,则说明是元素节点。则调用 createElement 方法创建对应的节点,接下来就通过 appendChild 方法插入到指定父节点中。假如父元素已经在视图中,那么把元素插入到它下面将会自动渲染出来;假如 vNode 的 isComment 属性是 true,则体现解释节点;都不是则是文本节点;
通常元素里面会有子节点,所以这里涉及一个递归的过程,也就是将 vNode 中的 children 依次遍历,创建节点,然后插入到父节点(父节点也就是刚刚创建出的 dom 节点)中,一层一层的递归进行。
请看源码:
  1. // 创建元素
  2. function createElm (
  3.   vnode,
  4.   insertedVnodeQueue,
  5.   parentElm,
  6.   refElm,
  7.   nested,
  8.   ownerArray,
  9.   index
  10. ) {
  11.   if (isDef(vnode.elm) && isDef(ownerArray)) {
  12.     // This vnode was used in a previous render!
  13.     // now it's used as a new node, overwriting its elm would cause
  14.     // potential patch errors down the road when it's used as an insertion
  15.     // reference node. Instead, we clone the node on-demand before creating
  16.     // associated DOM element for it.
  17.     vnode = ownerArray[index] = cloneVNode(vnode);
  18.   }
  19.   vnode.isRootInsert = !nested; // for transition enter check
  20.   if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  21.     return
  22.   }
  23.   var data = vnode.data;
  24.   var children = vnode.children;
  25.   var tag = vnode.tag;
  26.   // 有 tag 属性,表示是元素节点
  27.   if (isDef(tag)) {
  28.     vnode.elm = vnode.ns
  29.       ? nodeOps.createElementNS(vnode.ns, tag)
  30.       // 创建元素。nodeOps 涉及到跨平台
  31.       : nodeOps.createElement(tag, vnode);
  32.     setScope(vnode);
  33.     /* istanbul ignore if */
  34.     {
  35.       // 递归创建子节点,并将子节点插入到父节点上
  36.       createChildren(vnode, children, insertedVnodeQueue);
  37.       if (isDef(data)) {
  38.         invokeCreateHooks(vnode, insertedVnodeQueue);
  39.       }
  40.       // 将 vnode 对应的元素插入到父元素中
  41.       insert(parentElm, vnode.elm, refElm);
  42.     }
  43.   // isComment 属性表示注释节点
  44.   } else if (isTrue(vnode.isComment)) {
  45.     vnode.elm = nodeOps.createComment(vnode.text);
  46.     // 插入父节点
  47.     insert(parentElm, vnode.elm, refElm);
  48.   // 否则就是子节点
  49.   } else {
  50.     vnode.elm = nodeOps.createTextNode(vnode.text);
  51.     // 插入父节点
  52.     insert(parentElm, vnode.elm, refElm);
  53.   }
  54. }
  55. // 递归创建子节点,并将子节点插入到父节点上。vnode 表示父节点
  56. function createChildren (vnode, children, insertedVnodeQueue) {
  57.   if (Array.isArray(children)) {
  58.     if (process.env.NODE_ENV !== 'production') {
  59.       checkDuplicateKeys(children);
  60.     }
  61.     // 依次创建子节点,并将子节点插入到父节点中
  62.     for (var i = 0; i < children.length; ++i) {
  63.       createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
  64.     }
  65.   } else if (isPrimitive(vnode.text)) {
  66.     nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
  67.   }
  68. }
复制代码
删除节点 - 源码分析


删除节点非常简朴。直接看源码:
  1. // 删除一组指定节点
  2. function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
  3.   for (; startIdx <= endIdx; ++startIdx) {
  4.     var ch = vnodes[startIdx];
  5.     if (isDef(ch)) {
  6.       if (isDef(ch.tag)) {
  7.         removeAndInvokeRemoveHook(ch);
  8.         invokeDestroyHook(ch);
  9.       } else { // Text node
  10.         // 删除个节点
  11.         removeNode(ch.elm);
  12.       }
  13.     }
  14.   }
  15. }
  16. // 删除单个节点
  17. function removeNode (el) {
  18.   var parent = nodeOps.parentNode(el);
  19.   // element may have already been removed due to v-html / v-text
  20.   if (isDef(parent)) {
  21.     // nodeOps里封装了跨平台的方法
  22.     nodeOps.removeChild(parent, el);
  23.   }
  24. }
复制代码
以上就是vue 假造DOM快速入门的详细内容,更多关于vue 假造DOM的资料请关注脚本之家别的相干文章!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作