react虚拟DOM的原理, 虚拟DOM是什么? 怎么理解虚拟DOM?
-
是什么? 顾名思义,虚拟DOM就是虚拟节点,使用js对象来模拟的DOM节点,存储在内存中,最终会根据这些虚拟的dom节点渲染出真实的DOM节点 本质上是一个Js对象
-
怎么做?
js对象是如何模拟dom节点?
比如我们使用jsx语法写出了一个列表
<ul id="list"> <li className="li">hello world</li> </ul>那么会被表示成一个如下的js对象
vNode = { tageName: 'ul', props: { id: 'list }, children: [ { tagName: 'li', props: { className: 'li', }, children: ['hi'] } ] }为什么写了这个标签就会被转换成这样的对象呢?
因为jsx的语法会被转译成creaeElement函数调用,如下:
React.createElement('ul', { id: 'list'}, React.createElement('li', { class: 'li'}, 'hello world') )(这里是babel或者webpack的一些loader实现的)
所以实际上,我们是最终通过creatElement这个方法来实现由标签转化成虚拟dom的。creatElement可以嵌套。
如何将虚拟节点变成真实dom节点
通过一个render方法
function render(vdom) { //如果是字符串或者数字,创建一个文本节点 if (typeof vdom === 'string' || typeof vdom === 'number') { return document.createTextNode(vdom); } //创建真实的dom const { tagName, props, children } = vdom; const element = document.createElement(tag); //设置该节点的属性 setProps(element, props); //遍历子节点,并获取创建真实DOM,插入到当前节点 children.map(render).forEach(element.appendChild.bind(element)); //递归的遍历过程 //虚拟dom中缓存真实dom节点 vdom.dom = element; //返回真实dom return element; }如果节点发生了变化,那么就会产生一个新的虚拟节点,但是我们不会把这个虚拟节点直接渲染到页面,而是经过diff算法得到一个patch(补丁)再更新到真实节点上。 这个过程会进行一个新旧虚拟dom的对比,然后得到一个最精简的dom的更改,这里可能就是一个局部的更改,比如就是更改某个文本而已,然后更新到真实dom上。
-
解决了什么问题 a. dom操作性能的问题,我们在以前用原生js或者jquery写代码的时候,会频繁地使用操作dom的方法,比如新增节点,删除节点等等,但是我们的写法可能性能很有问题,那么通过虚拟dom和diff算法,就可以减少很多不必要的dom操作,保证了性能不会太差。相当于规范了我们的操作,以最理想的方式去更新我们的dom。 (因为最终还是要操作真实的dom, 所以只能说保证让性能不太差,而不能保证一定不会有性能问题,但是可以让一些经验较少的程序员写出的代码性能不会有大问题)。
b. 避免dom操作不方便的问题,因为要使用各种DOM的API, 需要记很多的API, 非常繁琐, 而现在我们只需要记住去setState,去更新数据就够了。
-
优点?(比其他的解决方案好在哪里?) a. 让react带来了跨平台的能力,因为虚拟节点除了可以渲染为真实的dom节点外,还可以渲染成其他的东西。就是拿到虚拟dom这个对象去渲染的时候,如果是在h5页面的话,div就被渲染成div, 如果在android平台的话,我们也可以把它渲染成android平台上的某个标签 view啊什么的。在ios上的话 就可以是其他的标签了。 所以说可以实现跨平台的能力。 b. 让dom操作的整体性能更改,减少不必要的dom操作,保证性能下线
-
缺点? 在性能要求极高的情况下还是要操作真实dom (不算什么缺点) react为虚拟dom创建了合成事件, react的事件并不是真实的dom事件, 所有react的事件都是绑定在了根元素上,那么所有的onClick其实并不是绑定在button或者div上面的,是直接绑定在根元素上的,自动实现了事件委托。 如果同时使用原生dom事件和合成事件,可能会出bug,比如在其中一个事件里面阻止冒泡了,但是另一个事件还是会触发,因为两个事件是不同的系统。 (这个是需要注意的。)
-
如何解决缺点? 不用react??
dom diff算法?
- 是什么? 就是用来对比两棵虚拟dom树的算法。当组件变化的时候,会render出一个新的虚拟DOM,diff算法对比新旧虚拟dom之后,得到一个patch, 然后react用patch来更新真实的dom。
- 怎么实现?
a. 首先对比两棵树的根节点
i. 如果根节点类型变了,比如div变成了span,那么直接认为整棵树都变了,不再进行子节点的对比。此时直接删除对应的真实DOM树,创建新的真实DOM树。 ii. 如果根节点类型没有变,检查属性是否改变 1. 没有变, 保留对应的真实节点 2. 改变了, 更新改节点的属性,不重新创建节点 (如果是style属性发生了改变,react也只会更新改变的,比如style="margin:0; paring:0" 变成了style="margin:1px; padding: 0;", react 也只会更新改变的。将margin做更新 而不是重写整个style)b. 然后同时遍历两棵树的子节点,每个节点的对比过程同上。
i. 情况一 ``` <ul> <li>a</li> <li>b</li> </ul> ``` 变成了 ``` <ul> <li>a</li> <li>b</li> <li>c</li> </ul> ``` 那么react会依次比较a-a b-b ,发现c是新增的,最终会创建真实的c节点插入到页面 ii. 情况二 ``` <ul> <li>b</li> <li>c</li> </ul> ``` 变成 ``` <ul> <li>a</li> <li>b</li> <li>c</li> </ul> ``` react 会先对比b - a 然后就删除了b文本,创建a文本, 然后对比c-b, 会删除c文本,创建b文本,最后发现多了一个c, 就会新创建c,添加到最后。 (注意 不是变对比变删除新建的,而是会把操作汇总到patch里,再进行最终的DOM操作) 其实你会发现只需要新增一个a ,保留b和c即可。 那为什么react做不到呢? 因为需要你增加一个Key,这样react就可以做到了! ``` <ul> <li key="b">b</li> <li key="c">c</li> </ul> ``` 变成 ``` <ul> <li key="a">a</li> <li key="b">b</li> <li key="c">c</li> </ul> ``` 加上key的节点,react会怎么对比呢? react先对比key,发现Key只新增了一个,那么久保留b和c,新增a。
vue的dom diff算法源码分析之----双端交叉对比
重复下面的对比过程,直到两个数组中任一数组的头指针超过尾指针,循环结束
- 头头对比 对比两个数组的头部,如果找到,把新节点patch到旧节点,头指针后移
- 尾尾对比 对比两个数组的尾部,如果找到, 把新节点patch到旧节点,尾指针前移
- 旧尾新头对比 交叉对比,旧尾新头,如果找到,把新节点patch到旧节点,新尾指针前移,旧头指针后移
- 旧头新尾对比 交叉对比,旧头新尾, 如果找到, 把新节点patch到旧节点,新尾指针前移,旧头指针后移
- 利用key进行对比
react dom diff 与 vue 的dom diff 的区别
- react 是从左到右遍历的对比 vue 是双端交叉的对比
- vue 整体效率要比react更高, 举例说明:假设有N个子节点,我们只是要把最后子节点移到第一个,那么 a. react需要借助Map进行key搜索找到匹配项,然后复用节点 b. Vue会发现移动,直接复用节点