摘要:最后删除新的树中不存在的节点。而中会记录对其做了相应的优化,节点的的情况下,不做移动操作。这种情况,在中得到了优化,通过四个指针,在每次循环中先处理特殊情况,并通过缩小指针范围,获得性能上的提升。
上篇文章已经介绍过idff的处理逻辑主要分为三块,处理textNode,element及component,但具体怎么处理component还没有详细介绍,接下来讲一下preact是如何处理component的。
组件的diff通过学习元素节点的diff操作,我们不妨大胆猜测一下,组件是做了如下diff操作:
组件不同类型或者不存在就创建,走相应的生命周期钩子
比较组件的属性
比较组件的孩子
事实上和我们的猜想很相似,在进行下一步之前,我们先了解下preact中的数据结构:
// 如下JSX// App组件的实例,会有以下属性 { base, // 对应组件渲染的dom _component, // 指向Child组件 } // Child组件有以下属性 { base, // 与App组件实例指向同一个dom _parentComponent, // 指向App组件 } // 对应的dom节点,即前文中的base对象 { _component // 指向App组件,而不是Child组件 }
然后我们看一下buildComponentFromVNode逻辑:
如果组件类型相同调用setComponentProps
如果组件类型不同:
回收老的组件
创建新的组件实例
调用setComponentProps
回收老的dom
返回dom
function buildComponentFromVNode(dom, vnode, context, mountAll) { let c = dom && dom._component, originalComponent = c, oldDom = dom, isDirectOwner = c && dom._componentConstructor === vnode.nodeName, // 组件类型是否变了 isOwner = isDirectOwner, props = getNodeProps(vnode); while (c && !isOwner && (c = c._parentComponent)) { // 如果组件类型变了,一直向上遍历;看类型是否相同 isOwner = c.constructor === vnode.nodeName; } // 此时isOwner就代表组件类型是否相同 // 如果组件类型相同,只设置属性;然后将dom指向c.base if (c && isOwner && (!mountAll || c._component)) { setComponentProps(c, props, 3, context, mountAll); dom = c.base; } else { if (originalComponent && !isDirectOwner) { // 组件类型不同就先卸载组件 unmountComponent(originalComponent); dom = oldDom = null; } // 创建组件的主要逻辑就是return new vnode.nodeName() c = createComponent(vnode.nodeName, props, context); if (dom && !c.nextBase) { c.nextBase = dom; // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229: oldDom = null; } setComponentProps(c, props, 1, context, mountAll); dom = c.base; if (oldDom && dom !== oldDom) { oldDom._component = null; recollectNodeTree(oldDom, false); } } return dom; }
可以看到组件进一步diff的核心逻辑在setComponentProps方法中,setComponentProps大致做了两件事:
调用渲染前的生命周期钩子: componentWillMount 与 componentWillReceiveProps
调用renderComponent
renderComponent主要逻辑为:
调用shouldComponentUpdate 或 componentWillUpdate生命周期钩子
调用组件的render方法
如果render的结果是一个组件,做类似与buildComponentFromVNode的操作
如果render的结果是dom节点,调用diff操作
替换新的节点,卸载老的节点或组件
为组件的base添加组件引用_component
调用组件的生命周期钩子componentDidUpdate,componentDidMount。
至此,我们已经大致了解了preact的大致全流程,接下来我们看一下它的diffChildren的算法:
将原始dom的子节点分为两部分,有key的放在keyed map里面,没有key的放在children数组里面。
遍历vchildren,通过key找到keyed中的child,如果child不存在,从children中取出相同类型的子节点
对child与vchild进行diff,此时得到的dom节点就是新的dom节点
然后与老的dom节点对应的节点比较,操作dom树。
最后删除新的dom树中不存在的节点。
function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) { let originalChildren = dom.childNodes, children = [], keyed = {}, keyedLen = 0, min = 0, len = originalChildren.length, childrenLen = 0, vlen = vchildren ? vchildren.length : 0, j, c, f, vchild, child; if (len !== 0) { for (var i = 0; i < len; i++) { var _child = originalChildren[i], props = _child.__preactattr_, key = vlen && props ? _child._component ? _child._component.__key : props.key : null; if (key != null) { keyedLen++; keyed[key] = _child; } else if (props || (_child.splitText !== undefined ? isHydrating ? _child.nodeValue.trim() : true : isHydrating)) { children[childrenLen++] = _child; } } } // 遍历虚拟dom节点 // 取child(有key,证明它两个是要对应比较的) // 如果child和originchildren[i]比较 // originchild没有,多余,否则插入到originchild前面 if (vlen !== 0) { for (var i = 0; i < vlen; i++) { vchild = vchildren[i]; child = null; // attempt to find a node based on key matching var key = vchild.key; if (key != null) { if (keyedLen && keyed[key] !== undefined) { child = keyed[key]; keyed[key] = undefined; keyedLen--; } } // attempt to pluck a node of the same type from the existing children else if (!child && min < childrenLen) { for (j = min; j < childrenLen; j++) { //从min往后开始遍历,如果是相同类型的节点就拿出来,那个位置设为undefined if (children[j] !== undefined && isSameNodeType(c = children[j], vchild, isHydrating)) { child = c; children[j] = undefined; if (j === childrenLen - 1) childrenLen--; if (j === min) min++; break; } } } // morph the matched/found/created DOM child to match vchild (deep) child = idiff(child, vchild, context, mountAll); f = originalChildren[i]; if (child && child !== dom && child !== f) { if (f == null) { dom.appendChild(child); } else if (child === f.nextSibling) { removeNode(f); } else { dom.insertBefore(child, f); } } } } // remove unused keyed children: // keyedLen标识老的集合中还有的元素,但没在新的集合中使用 if (keyedLen) { for (var i in keyed) { if (keyed[i] !== undefined) recollectNodeTree(keyed[i], false); } } // remove orphaned unkeyed children: // min代表拿走的元素 while (min <= childrenLen) { if ((child = children[childrenLen--]) !== undefined) recollectNodeTree(child, false); } }
从上面可以看出,preact只处理了常见的使用场景,没有做特别的优化措施,这也导致它在一些情况下的性能比react低,如:从a b到b a。
而react中会记录lastIndex,对其做了相应的优化,节点的Index > lastIndex的情况下,不做移动操作。
但是如果react中有length > 2,最前面的节点位置与最后面的节点位置互换的情况下,由于index一直小于lastIndex,就会失去上述的优化效果。
这种情况,在snabbdom中得到了优化,snabbdom通过oldStartIdx,oldEndIdx,newStartIdx,newEndIdx四个指针,在每次循环中先处理特殊情况,并通过缩小指针范围,获得性能上的提升。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95134.html
摘要:对回收的处理在中,回收调用了两个方法,节点的回收一般会调用,组件的回收会调用。个人理解从以上源码阅读中我们可以看到,最大的性能问题在于递归的,中的与也是为了缓解这个问题。为不同类型的更新分配优先级。 对回收的处理 在preact中,回收调用了两个方法,dom节点的回收一般会调用recollectNodeTree,组件的回收会调用unmountComponent。 preact复用dom...
摘要:是一个最小的库,但由于其对尺寸的追求,它的很多代码可读性比较差,市面上也很少有全面且详细介绍的文章,本篇文章希望能帮助你学习的源码。建议与源码一起阅读本文。 作为一名前端,我们需要深入学习react的运行机制,但是react源码量已经相当庞大,从学习的角度,性价比不高,所以学习一个react mini库是一个深入学习react的一个不错的方法。 preact是一个最小的react mi...
摘要:的选择器允许单个线程监视多个输入通道。一旦执行的线程已经超过读取代码中的某个数据片段,该线程就不会在数据中向后移动通常不会。 1、引言 很多初涉网络编程的程序员,在研究Java NIO(即异步IO)和经典IO(也就是常说的阻塞式IO)的API时,很快就会发现一个问题:我什么时候应该使用经典IO,什么时候应该使用NIO? 在本文中,将尝试用简明扼要的文字,阐明Java NIO和经典IO之...
摘要:,,面向切面编程。,切点,切面匹配连接点的点,一般与切点表达式相关,就是切面如何切点。例子中,注解就是切点表达式,匹配对应的连接点,通知,指在切面的某个特定的连接点上执行的动作。,织入,将作用在的过程。因为源码都是英文写的。 之前《零基础带你看Spring源码——IOC控制反转》详细讲了Spring容器的初始化和加载的原理,后面《你真的完全了解Java动态代理吗?看这篇就够了》介绍了下...
摘要:经过一次冒泡排序,最终在无序表中找到一个最大值,第一次冒泡结束。也是我们后面要说的冒泡排序的优化。冒泡排序只执行第一层循环,而不会执行第二层循环。因此冒泡排序的时间复杂度是。 showImg(https://user-gold-cdn.xitu.io/2019/6/19/16b6f986df6880f9?w=640&h=142&f=gif&s=17175);showImg(https:...
阅读 1201·2021-11-24 09:39
阅读 348·2019-08-30 14:12
阅读 2562·2019-08-30 13:10
阅读 2377·2019-08-30 12:44
阅读 919·2019-08-29 16:31
阅读 804·2019-08-29 13:10
阅读 2396·2019-08-27 10:57
阅读 3129·2019-08-26 13:57