Vue虚拟DOM

1.什么是虚拟dom

  • 模板转换成视图的过程
    在这里插入图片描述

1.Vue.js将tetmplate模板编译为渲染函数Render,执行渲染函数生成一棵虚拟dom树
2.在对Model操作时,触发对应Dep中的Watcher对象,Watcher对象会调用对应的update更新视图

渲染函数:用来生成虚拟树
vnode虚拟节点:代表一个真实的dom节点,通过createElement方法将vnode渲染成dom节点
patch虚拟dom最核心部分,可以将vnode渲染成真实的dom,整个过程就是对比新旧虚拟节点的不同,根据对比结果找到需更新的节点

  • Virtual DOM 是什么

Virtual DOM实际是一棵以js对象(vnode节点)为基础的树,用对象属性来描述节点,至少包含标签名Tag属性attrs子元素对象children三个属性
在这里插入图片描述
在这里插入图片描述

2.为什么要引入虚拟dom

虚拟节点的最终目标就是将虚拟节点渲染至视图上。如:一个ul标签有多个li标签,其中一个li标签发生变化,如果使用新的ul替代旧的ul,因为不必要的dom操作造成性能上的浪费
为了不必要的dom操作,虚拟dom在虚拟节点映射至视图的过程中,将虚拟节点与上一次渲染视图所需要的旧虚拟节点对比,找到需要更新的节点来进行dom操作

虚拟dom在vue.js中主要完成了两件事:

1)提供与真实dom节点所对应的虚拟节点vnode

2)将虚拟节点vnode和旧虚拟节点对比,然后更新视图

优势:
若一次操作中有N次更新dom行为,虚拟dom不会立即执行操作dom,而是将这N次更新的diff内容保存至本地的一个js对象中,将这个js对象一次性attch到dom树上,再进行后续操作。

1.跨平台的优势
2.将dom对比操作放在js层,提高效率
3.提升渲染性能

  • diff算法

vue的diff算法基于snabbdom,仅在同级的vnode间做diff,递归进行,最终实现整个dom树的更新
在这里插入图片描述
diff算法的实现步骤:

  • 使用js对象结构表示dom树结构,然后使用此树构建一棵真正的dom树
  • 当状态变更的时候,重新构造dom树,将新的树和旧的树对比,记录差异
  • 把所记录的差异应用至所构建的真正dom树上,视图更新

diff两个核心函数实现
patch(container,vnode):初次渲染,将vdom渲染成真正的dom插入至容器中

function createElement(vnode){
	var tag = vnode.tag
	var attrs = vnode.attrs || {}
	var children =vnode.children || {}
	if(!tag){
		return null
	}
	// 创建真正的dom元素
var elem = document.createElement(tag) 
// 遍历属性
for (var attrName in attrs){
	if(attrs.hasOwnProperty(attrName)){
		 // 给 elem 添加属性
		 elem.setAttribute(attrName, attrs[attrName])
	}
}
// 子元素
children.forEach(function(childVnode)){
	 // 给 elem 添加子元素,如果还有子节点,则递归的生成子节点。
	 elem.appendChild(createElement(childVnode))  // 递归
}
 // 返回真实的 DOM 元素   
 return elem
	
}

patch(vnode,newVnode):再次渲染,将新的vnode和旧的vnode对比,然后将差异应用至真正的dom树上

// 这里我们只考虑vnode与newVnode如何对比的情况
function updateChildren(vnode, newVnode) {
    var children = vnode.children || []
    var newChildren = newVnode.children || []
  // 遍历现有的children
    children.forEach(function (childVnode, index) {
        var newChildVnode = newChildren[index]
  // 两者tag一样
        if (childVnode.tag === newChildVnode.tag) {
            // 深层次对比,递归
            updateChildren(childVnode, newChildVnode)
        } else { 
  // 两者tag不一样
           replaceNode(childVnode, newChildVnode) 
       }
    }
)}

3.Vue.js中的虚拟dom

  • vue中的虚拟dom如何创建?
    vue-loader可以在template中编写模板字符串,将内容提取至vue-template-compiler,Vue编译转换为渲染函数中的内容,执行这个函数得到vnode树,
<template>
	<img src='../image.png'>
</template>

// --------编译为
createElement('img', {
  attrs: {
    src: require('../image.png')
  }
})
  • vue中的Vnode类

vnode类型有 1)注释节点 2)文本节点 3)元素节点 4)组件节点 5)函数组件 6)克隆节点

    this.isComment = false; // true 代表注释节点
    this.isCloned = false; // true 代表克隆节点
    this.componentOptions = componentOptions; // 组件节点的选项参数,propsData、tag、children
    this.componentInstance = undefined;// 组件实例
 	this.context = context; // 当前组件的Vue实例
    this.fnContext = undefined;// 函数式组件实例
    this.fnOptions = undefined;// 函数式组件选项参数
  • patch

patch对比新旧vnode的差异,修改dom节点
修改dom节点需要完成三件事:
1)创建新节点
oldVnode不存在而vnode存在
当oldVnode和vnode完全不是同一个节点
2)删除已废弃的节点 vnode中不存在的节点
3)修改需更新的节点 vnode和oldVnode是同一个节点,进行详细对比

只有三种类型的节点会被创建并插入到DOM中:元素、文本、注释

  • 如何判断节点类型:
    1)vnode是否为元素节点,是否具有tag属性
    2)不具有tag属性则是文本或注释节点,根据vnode的isComment=true则为注释节点,false则是文本节点

  • 创建新增节点

  • createElement ——元素
  • createTextNode ——文本
  • createComment ——注释
  • 删除已废弃节点

function removeVnodes (vnodes, startIdx, endIdx) {
      for (; startIdx <= endIdx; ++startIdx) {
        var ch = vnodes[startIdx];
        if (isDef(ch)) {
           removeNode(ch.elm);
        }
      }
}

var nodeOps = {
    removeChild: removeChild,
    appendChild: appendChild,
    parentNode: parentNode
 }
function removeNode (el) {
      var parent = nodeOps.parentNode(el);
      // element may have already been removed due to v-html / v-text
      if (isDef(parent)) {
        nodeOps.removeChild(parent, el);
      }
    }


  • 修改需更新的节点

1 . vnode有文本属性,则无论oldVnode子节点是什么,直接setTextContent方法更新文本
2 .vnode是无children的元素,则oldVnode中无论有子节点还是文本,直接都删除即可。

  1. vnode是有children的元素
    (1) oldVnode没有children,那么oldVnode要么是空标签,要么有文本子节点,则直接清空,并将vnode中的children直接插入到视图中。
    (2)oldVnode有children,则需要详细的对比,可能会新增、删除、或者移动。

更新策略

对比两个子节点列表,首先循环newChildren,每循环一个新的子节点,就去oldChildren中找到相同的旧子节点

  • 找不到相同的旧子节点,则直接新增子节点,并插入到oldChildren中所未处理节点对应的DOM前面
  • 找到且位置相同,直接更新dom
  • 找到但位置不同,移动子节点Node.insertBefore()将节点移动到所有未处理节点对应DOM的最前面。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值