文末
js前端的重头戏,值得花大部分时间学习。
推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。
学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。
面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。
这是288页的前端面试题
总结一下,在第一轮遍历渲染树时,我们在先序遍历时创建Node实例,然后计算节点的left,在后序遍历时计算每个节点的所有子节点的所占的总高度。
接下来开启第二轮遍历,这轮遍历可以计算所有节点的top,因为此时节点树已经创建成功了,所以可以不用再遍历渲染树,直接遍历节点树:
// 第二次遍历walk(this.root, null, (node, parent, isRoot, layerIndex) => { if (node.children && node.children.length > 0) { // 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半 let top = node.top + node.height / 2 - node.childrenAreaHeight / 2 let totalTop = top + marginY// node.childrenAreaHeight是包括子节点整体前后的间距的 node.children.forEach((cur) => { cur.top = totalTop totalTop += cur.height + marginY// 在上一个节点的top基础上加上间距marginY和该节点的height }) }}, null, true)
事情到这里并没有结束,请看下图:
可以看到对于每个节点来说,位置都是正确的,但是,整体来看就不对了,因为发生了重叠,原因很简单,因为【二级节点1】的子节点太多了,子节点占的总高度已经超出了该节点自身的高。
因为【二级节点】的定位是依据【二级节点】的总高度来计算的,并没有考虑到其子节点,解决方法也很简单,再来一轮遍历,当发现某个节点的子节点所占总高度大于其自身的高度时,就让该节点前后的节点都往外挪一挪。
比如上图,假设子节点所占的高度比节点自身的高度多出了100px,那我们就让【二级节点2】向下移动50px,如果它上面还有节点的话也让它向上移动50px,需要注意的是,这个调整的过程需要一直往父节点上冒泡,比如:
【子节点1-2】的子元素总高度明显大于其自身,所以【子节点1-1】需要往上移动,这样显然还不够,假设上面还有【二级节点0】的子节点,那么它们可能也要发生重叠了。
而且下方的【子节点2-1-1】和【子节点1-2-3】显然挨的太近了,所以【子节点1-1】自己的兄弟节点调整完后,父节点【二级节点1】的兄弟节点也需要同样进行调整,上面的往上移,下面的往下移,一直到根节点为止:
// 第三次遍历walk(this.root, null, (node, parent, isRoot, layerIndex) => { // 判断子节点所占的高度之和((除去子节点整体前后的margin))是否大于该节点自身 let difference = node.childrenAreaHeight - marginY * 2 - node.height // 大于则前后的兄弟节点需要调整位置 if (difference > 0) { this.updateBrothers(node, difference / 2) }}, null, true)
updateBrothers用来向上递归移动兄弟节点:
updateBrothers(node, addHeight) { if (node.parent) { let childrenList = node.parent.children // 找到自己处于第几个节点 let index = childrenList.findIndex((item) => { return item === node }) childrenList.forEach((item, _index) => { if (item === node) { return } let _offset = 0 // 上面的节点往上移 if (_index < index) { _offset = -addHeight } else if (_index > index) { // 下面的节点往下移 _offset = addHeight } // 移动节点 item.top += _offset // 节点自身移动了,还需要同步移动其所有下级节点 if (item.children && item.children.length) { this.updateChildren(item.children, ‘top’, _offset) } }) // 向上遍历,移动父节点的兄弟节点 this.updateBrothers(node.parent, addHeight) }}
// 更新节点的所有子节点的位置updateChildren(children, prop, offset) { children.forEach((item) => { item[prop] += offset if (item.children && item.children.length) { this.updateChildren(item.children, prop, offset) } })}
到此【逻辑结构图】的整个布局计算就完成了,当然,有一个小小小的问题:
就是严格来说,某个节点可能不再相对于其所有子节点居中了,而是相对于所有子孙节点居中,其实这样问题也不大,实在有强迫症的话,可以自行思考一下如何优化(然后偷偷告诉笔者),这部分完整代码请移步LogicalStructure.js。
节点连线
节点定位好了,接下来就要进行连线,把节点和其所有子节点连接起来,连线风格有很多,可以使用直线,也可以使用曲线,直线的话很简单,因为所有节点的left、top、width、height都已经知道了,所以连接线的转折点坐标都可以轻松计算出来:
我