一、虚拟 DOM
1.虚拟DOM的优点
- 减少DOM操作
1.虚拟 DOM 可以将多次操作合并为一次操作,比如你添加 1000 个节点,却是一个接一个操作的
2.虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加1000个节点,其实只有10个是新增的 - 跨平台
虚拟DOM 不仅可以变成 DOM,还可以变成小程序、iOS应用、安卓应用,因为虚拟DOM本质上是一个JS对象
2.虚拟DOM长什么样
- React的虚拟DOM
表示:一个标签为div,子元素为2个span,class为red,点击事件调用一个函数的DOM
const vNode = {
key: null,
props: {
children: [ // 子元素们
{ type: 'span', ... },
{ type: 'span', ... }
],
className: "red" // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
- Vue的虚拟DOM
和React没什么区别,只是写法不同
const vNode = {
tag: "div", // 标签名 or 组件名
data: {
class: "red", // 标签上的属性
on: {
click: () => {} // 事件
}
},
children: [ // 子元素们
{ tag: "span", ... },
{ tag: "span", ... }
],
...
}
3.如何创建虚拟DOM
- React.createElement
createElement('div',{className:'red',onClick:()=> {}},[
createElement('span', {}, 'span1'),
createElement('span', {}, 'span2')
]
)
- Vue(只能在 render 函数里得到 h)
h('div', {
class: 'red',
on: {
click: () => { }
},
}, [h('span',{},'span1'), h('span', {}, 'span2'])
4.用 JSX 简化创建虚拟DOM
- 简化后创建和真实的DOM很像
真实DOM不支持花括号和里面的JS语法 - React JSX
通过 babel 转为 createElement 形式
React默认就支持JS语法
<div className="red" onClick={fn}>
<span>span1</span>
<span>span2</span>
</div>
- Vue Template
通过 vue-loader 转为 h 形式
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
5.总结
- 虚拟DOM是什么?
一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听、子元素们,以及其他属性 - 虚拟DOM有什么优点?
1.能减少不必要的DOM操作
2.能跨平台渲染 - 虚拟DOM有什么缺点?
1.需要额外创建函数,如 create Element 或 h,但可以通过 JSX 来简化成 XML 写法
2.XML 写法的缺点:严重依赖打包工具,因为JS不认识该语法
6.额外的小tip
6.1 使用原生JS插入节点
- JS用时:代码执行完毕,通知浏览器执行
- 浏览器用时:浏览器真正把节点插到页面中
- 总结1:JS执行是很快的,只是浏览器在渲染页面的时候让页面不可交互
6.2 使用 React 和 Vue 插入节点
- 当规模合理的时候,React和Vue的虚拟DOM更快
因为会减少不必要的操作 - 当规模很大的时候,原生JS操作更具有稳定性,且浏览器不会崩溃
Vue也很快,接近原生JS
二、DOM diff
- 虚拟 DOM 的对比算法
1.什么是 DOM diff
- 就是一个函数,我们称之为 patch
- patches = patch(oldVNode,newVNode)
- patches 就是运行的 DOM 操作
- 只要给两个虚拟DOM(新旧),就可以返回对应的DOM操作(patches)
全部执行完才挨个执行
[
//插入节点
{type: 'INSERT', vNode: ... },
//修改文本
{type: 'TEXT', vNode: ... },
//更新属性
{type: 'PROPS', propsPatch: [...]}
]
2.DOM diff 的大概逻辑
- Tree diff
将新旧两棵树逐层对比,找出哪些节点需要更新
如果节点是组件就看 Component diff
如果节点是标签就看 Element diff - Component diff
如果节点是组件,就先看组件类型
类型不同直接替换(删除旧的)
类型相同则只更新属性
然后深入组件做 Tree diff(递归) - Element diff
如果节点是原生标签,则看标签名
标签名不同直接替换,相同则只更新属性
然后进入标签后代做 Tree diff(递归)
3.DOM diff 的缺点
- 同级节点对比存在 bug
- 举例
1.想要删掉dive中的第一个子元素:span-hello
2.并非是直接删掉第一个,然后第二个诺过去
3.真实步骤
——对比div:无变化——对比第一个span:无变化——对比第二个span:有变化——对比hello:有变化
4.执行完后,记录有变化的地方
——删掉第二个span
——将第一个span的text改为world
- bug
1.当删掉第二个子组件,却出现第二个子组件的data出现在原来的第三个子组件上
2.按照上面的步骤,这就是因为删掉的原本就是第三个组件
3.第二个组件中的input没有被DOM diff修改到。
- 出现bug的原因
计算机默认以下标来区分元素,而下标是会改变的 - 解决:给每个子组件添加 key
4.DOM diff 中 key 的问题
- 主动标记,给子组件取一个名字(key),计算机才能识别删掉的是第二个子组件
- 步骤
1.原本数组:[{id: 1,value: 1},{id: 2,value: 2},{id: 3,value: 3}]
2.点击删除后:[{id: 1,value: 1},{id: 3,value: 3}] - 原因
1.首先计算机发现id从 1 2 3 变成了 1 3
2.然后一次对比 id:1 和 id:3 的项,发现没有变化
3.最后,计算机得出结论:第二项被删除了