关于vue.js:Vue源码虚拟-Dom解析

什么是虚构DOM

  • Virtual DOM 是应用 JS 对象形容实在 DOM (一般的JS 对象,形容DOM 构造)
  • Vue 中的 虚构Dom 借鉴 Snabbdom, 并增加了Vue.js 的个性。(借鉴模块机制,钩子函数,diff算法等,增加例如指令,组件机制新个性)

为什么要应用 虚构DOM

  • 防止间接操作 DOM, 进步开发效率 (只关注业务代码实现,不须要关注 dom 浏览器兼容性问题)
  • 作为一个中间层能够跨平台 (Web Weex挪动端平台 SSR渲染)
  • 虚构 DOM 不肯定能够进步性能

    • 首次渲染时会减少开销 (须要保护一层额定的虚构dom)
    • 简单视图状况下晋升渲染性能 (频繁dom操作,通过diff算法,比照新旧两个虚构dom树差别,并更新差别)

h函数

vm = new Vue({
  el:"#app",
  render(h){
    // h(tag, data, children)   // tag元素标签, data元素属性,  children 数组则示意子元素,字符串示意元素内容
    // return h('h1',this.msg) // 省略 data
    // return h('h1', {domProps:{ innerHTML:this.msg }}) // dom属性
    // return h('h1', {attrs:{ id: "title" }}, this.msg) // 标签属性和dom内容
    
    let vnode = h('h1', {attrs:{ id: "title" }}, this.msg)
    console.log(vnode)
    return vnode;
  },
  data:{
    msg:"hello world"
  }
})

输入后果

  • 办法总结 vm.$createElement(tag,data,children,normalizeChildren)

    • tag 标签名或者组件对象
    • data 形容tag,能够设置 DOM属性或者标签属性
    • children 示意tag中的文本内容,或者子节点
  • 返回js对象(vnode对象)总结

    • tag:标签名,纯文本为undefined
    • data:形容配置
    • children:子节点
    • text:文本
    • elm:实在dom
    • key:元素复用
    • 其余

渲染地位

  • 在 core/instance/lifecycle.js 中定义的

    updateComponent = () => {
    // 这是个回调,wather外面会调用
    // _render 或获取虚构dom  _update将虚构dom转为实在dom
    vm._update(vm._render(), hydrating)
    }

解析 vm._render 办法

  • 定位在 core/instance/render.js
  • 此办法外部会获取用户传入的render,并执行,返回一个vnode对象

    vnode = render.call(vm._renderProxy, vm.$createElement)
  • 外部this执行 vm 实例,参数是一个函数

    // 用户传递的render函数时调用
    vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  • createElement办法中会对 传入的data做解决,进行铺平,而后调用 _createElement办法创立 VNode,并返回

解析 VNode创立 _createElement

  • 外部对 tag 进行判断
  • 带 is 为响应式组件赋值给tag
  • 如果 children 是多维数组,进行拍平
  • 最初创立 VNode 并返回

解析 vm._update

  • 获取了 VNode 对象后,会通过 _update渲染挂载实在dom
  • _update 定义在 core/instance/lifecycle.js
  • 外部进行 首次渲染 和 数据更新操作
  • 应用 vm.__patch__进行新老节点比拟

解析 vm.__patch__ 办法

  • patch函数,定义在platforms/web/runtime/index.js

    Vue.prototype.__patch__ = inBrowser ? patch : noop
  • patch函数通过 createPatchFunction 办法生成(办法为高阶函数,柯里化函数)
  • 此办法传入一个对象。对象内蕴含根本的模块和平台相干模块,例如 指令,ref,属性,款式,事件,以及 Snabbdom没有的 transition

解析 createPatchFunction 办法

  • 定位在 core/vdom/patch.js
  • 此函数相似于 Snabbdom中的init ,最终返回了patch函数,(函数返回函数,高阶)
  • 源码 line 700 左右

解析 patch 函数

  • 判断 新节点是否存在,不存在把对应的老节点移除
  • 判断 老节点不存在,新节点是否存在,如果存在, 调用 createElm创立新VNode
  • 判断 老节点存在,老节点是vnode还是实在 dom,
  • 如果是 vnode 并且, oldVnode和vnode是雷同节点,进行比拟,通过 patchVnode
  • 如果不是 vnode 是 dom,把 dom 转化成 vnode
  • 获取 vnode 的父dom,确定 到时候 vnode要插入的地位

解析 patch函数中的两个办法 createElm 和 patchVnode

createElm

  • 如果节点被渲染过并且还有子节点,就克隆一份
  • 通过 createComponent 解决组件状况
  • 判断标签,正文节点,文本节点的 vnode创立

    • 1.标签:如果定义html不存在的标签,正告。否则创立 vnode对应 dom元素,通过 createElement,并设置 vnode 作用域。非 weex 平台,解决子dom,调用create 钩子,插入到 标签中
    • 2.正文:调用 createComment 并插入
    • 3.文本:调用 createTextNode 并插入

patchVnode

  • 如果是 vnode不是 dom ,并且 新旧 vnode key tag雷同,都有子节点那么 调用 patchVnode进行diff算法比拟
  • 办法外部会获取 新旧vnode 的子节点。
  • 判断新老节点 是否有文本,是文本节点,则替换文本
  • 新节点没有文本,

    • 新老节点都有子节点

      • 调用 updateCHildren 比照更新dom
    • 新节点有子节点,老节点没有子节点。

      • 查看新节点子节点是否有反复的 key
      • 老节点是否有text,清空文本
      • 把新节点下的子节点转化成 dom
    • 老节点有子节点,新节点没有子节点。

      • 移除老节点子节点,触发remove,destory钩子
    • 老节点有 text 清空文本

解析 updateChildren

  • 如果两个vnode 节点 都有子节点, 且 他们 key tag 雷同,则对子节点进行 diff算法比拟,最大限度重用
  • 参考地址

key的作用

<div id="app">
  <button @click="handler"></button>
  <ul>
    <li v-for="item in items">{{ item }}</li>
  </ul>
</div>

new Vue({
  el:"#app",
  data:{
    items:["a","b","c","d"]
  },
  methods:{
    handler(){
      this.items.splice(1,0,"x")
    }
  }
})

在未设置key 状况下

  • 视图批改2,3,4项,新增第4项 ,更新三次dom,插入了一个dom,一共操作了四次dom
  • abcd => axbc => axbcd

设置key 状况下

  • 从先向后比拟,比拟第二项时,因为key 不统一,并不是 sameVnode
  • 持续判断,从后向前比拟,abcd和axbcd 后三项一样,倒数第二项不一样 a 和 x , 并不是 sameVnode
  • 持续判断,老开始和新完结
  • 持续判断,老完结和新开始
  • 以上都不满足,老节点先遍历实现,把新增的节点addVnode 进dom树
  • dom操作只有1次,就是增加dom那次

设置key 比不设置 key dom操作少很多

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理