1、什么是虚拟DOM,怎么构成的
虚拟DOM是真实DOM在内存中的表示,简单来说,虚拟DOM就是个对象,由tag,props,children构成
<div id="app">
<p class="text">hello world!!!</p>
</div>
// 可转化成下面的虚拟DOM表示
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello world!!!'
]
}
]
}
复制代码
上面对象就是我们说的虚拟DOM,可表示成树形结构,原生 DOM 因为浏览器厂商需要实现众多的规范(各种 HTML5 属性、DOM事件),即使创建一个空的 div 也要付出昂贵的代价。虚拟 DOM 提升性能的点在于 DOM 发生变化的时候,通过 diff 算法比对 JavaScript 原生对象,计算出需要变更的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图
react
主流的虚拟DOM库都有一个h函数,用于降虚拟DOM,转化为真实DOM, react通过babel把jsx转换成h函数形式,即: react_createElement函数, 最后调用render函数将虚拟DOM插入htmlDOM树中,渲染页面。
示例代码通过babel编译jsx转换成react可处理的结构
const Page = () => {
return <div
className="levi"
style={{margin: '100px'}}
onClick={onClick}
>hello world</div>
}
export default Page
// babel编译后
const Page = () => {
return /*#__PURE__*/_react.default.createElement("div", {
className: "levi",
style: {
margin: '100px'
},
onClick: onClick
}, "hello world");
};
var _default = Page;
复制代码
2、谈谈你对DIFF算法理解
顾名思义:就是比较新老VDOM变化,把变化的部分更新到视图上
diff的优化策略
- 针对于tree diff优化,react会忽略DOM节点跨层级的移动操作
- 针对于组件 diff优化,如果是不同的组件就会生成不同的组件,所以如果是不同的组件,就会被标记要修改,而不再细致的比较
- 针对于同一层级的一组子节点,可以通过对应的key进行区别
tree diff
针对于dom跨层级移动操作,diff算法会对相同层级的节点比较,如果某个节点不存在了,那么这个节点和其子节点是不会再有比较的,react只会考虑同层级的节点的位置变化,而对于不同层级的节点,只有创建和删除。如果某个节点下发现某个子节点A消失了,就会直接销毁子节点A。
component diff
如果是同一类型的组件,则会比较虚拟dom树,如果不是同一类型,那么这个组件将被标记为dirty组件,从而替换整个组件下的所有子节点。如果是同一类型的组件,可能存在虚拟dom没有任何变化,因此react通过shouldComponentUpdate来判断这个组件是否需要进行diff运算, 如下图,一旦react判断D和G是不同类型的组件,就不会比较两者的结构,而是直接删除componentD,重新创建组件G及其子节点
element diff
当节点处于同一级时,diff提供三种节点操作:插入,移动,删除, 如下图,老集合节点顺序为:a,b,c,d,新节点的顺序为:b,a,d,c。如果是传统的diff操作,那么就会对比老A和新B,发现B!=A于是创建并插入B至最新的集合中,删除老集合中的A,以此类推。
React提出,通过key来判断同一层级的同组子节点。如果是相同的节点,只需要移动位置即可
react主要用key来区分组件,相同的key表示同一个组件,react不会重新销毁创建组件实例,只可能更新。如果key不同,react会销毁已有的组件实例,重新创建组件新的实例
3、生命周期有哪些
4、 react 事件系统
5、 setState
- setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果
- 在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新
setState 是通过 enqueueUpdate 来执行 state 更新的,那 enqueueUpdate 是如何实现更新 state 的?继续往下走。
3、enqueueUpdate 如果当前正处于创建/更新组件的过程,就不会立刻去更新组件,而是先把当前的组件放在 dirtyComponent 里,这里也很好的解释了上面的例子,不是每一次的 setState 都会更新组件。否则执行 batchedUpdates 进行批量更新组件;
enqueueSetState: function(publicInstance, partialState, ...) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
if (internalInstance) {
(internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = [])).push(partialState),
enqueueUpdate(internalInstance);
}
}
function enqueueUpdate(component) {
// ...
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
复制代码
