什么是虚构 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 操作少很多