两道编程题引发的思考
今天做了两道编程题,拿到题目的第一感觉是真tm简单。经过这半年多被无情的吊打,已经开始怀疑人生了,今晚突然来这么一下,这久违的感觉,舒服~,就感觉自己还是能做一些事情的。但是故事并没有像我们想象的那样发展,明明知道它简单容易,却就是不能快速搞定它,或者说不能把它做好,做到自己都开始崇拜自己。这是什么原因呢?前端的很多东西看起来简简单单,等到一测试发现这里没考虑到,那里又走不通等等诸多问题。写代码是需要考虑逻辑的,需要站在计算机的角度看代码,它是如何执行的,遇到问题最先想到是断点,而不是打日志盲猜。打断点有助于培养计算机思维,久而久之就会形成一种特定的思考方式。这不仅有助于我们排查错误,更能帮助我们的完善代码逻辑和规范。因为你还没写代码就知道这一步要执行什么,哪里执行,怎么执行,后一步要在哪里执行,怎么执行。这也就是为什么那些牛逼大牛在需求评审的时候就能粗略的估计开发时长,等到技术评审的时候开发时间已经能精准的计算出来,很多坑都提前避免了,不用等到写着写着代码才发现这里有坑,又临时找产品开会。
我们不论是写公用方法或函数,还是写复用组件也好,无非就是让用户能用,在者就是用的爽,最好的莫过于让用户直呼“我艹~”。要了解我们服务的用户需要什么。作为一个程序员,能实现用户的需求应该是最基本的要求,并没有什么好值得炫耀,就像下面的题目,用户给出的测试用例我们能通过,用户给另外一个就卡了,但是你还是有办法解决,也就是这种用户给几个测试用例我们实现几个功能,这样用起来就非常不爽。再者就是我们要站在用户的角度考虑问题,ta真正需要的是一个什么样的东西,我们自己可以做一些边界判断和容错处理,测试用例可以自己给自己,也就是站在产品的角度思考程序如何写。然后继续进阶,我们可以比产品考虑更长远一些,乃至到最后可以提前预测一些坑,提前做好准备。当然业务千变万化,不能预测到的业务才是值得做的。业务一成不变,总整抄袭,老是做人家老掉牙的东西有啥意思。我也喜欢做新鲜有趣的东西。
话不多说,先看题目
第一题
DOM 的体积过大会影响页面性能,假如你想在用户关闭页面时统计(计算并反馈给服务器)
当前页面中元素节点的数量总和、
元素节点的最大嵌套深度以及最大子元素个数,
请用 JS 配合原生 DOM API 实现该需求
(不用考虑陈旧浏览器以及在现代浏览器中的兼容性,可以使用任意浏览器的最新特性;不用考虑 shadow DOM)。
比如在如下页面中运行后:
<html>
<head></head>
<body>
<div>
<span>f</span>
<span>o</span>
<span>o</span>
</div>
</body>
</html>
会输出:
{
totalElementsCount: 7,
maxDOMTreeDepth: 4,
maxChildrenCount: 3
}
编程实现(可以查阅相关 DOM API,但是不可以使用前端框架 or 类库):
*/
export function calculateDOMNodes() {
}
参考题解
function calculateDOMNodes() {
const map = {
totalElementsCount: 1,
maxDOMTreeDepth: 0,
maxChildrenCount: 0
}
let depth = 0;
const root = document.querySelector('html');
traverse(root, map, depth);
console.log(map)
}
function traverse(node, map, depth) {
let children = node.childNodes;
depth++;
map.maxDOMTreeDepth = depth > map.maxDOMTreeDepth ? depth : map.maxDOMTreeDepth
map.maxChildrenCount = node.childElementCount > map.maxChildrenCount ? node.childElementCount : map.maxChildrenCount;
for (let i = 0; i < children.length; i++) {
traverse(children[i], map, depth);
if (children[i].tagName && children[i].tagName !== 'SCRIPT') {
map.totalElementsCount++;
}
}
}
calculateDOMNodes();
第二题
计算多个区间的交集
区间用长度为2的数字数组表示,如[2, 5]表示区间2到5(包括2和5);
区间不限定方向,如[5, 2]等同于[2, 5];
实现getIntersection
,可接收多个区间,并返回所有区间的交集(用区间表示),如空集用null表示
示例:
getIntersection([5, 2], [4, 9], [3, 6]); // [4, 5]
getIntersection([1, 7], [8, 9]); // null
参考题解
function getIntersection(...arrs) {
let left = null;
let right = null;
arrs = arrs.map(item=>item.sort((a,b)=>a-b));
for(let i = 0; i < arrs.length-1; i++){
// 取大
left = arrs[i][0] <= arrs[i+1][0] ? arrs[i+1][0] : left;
// 取小
right = arrs[i][1] <= arrs[i+1][1] ? arrs[i][1] : right;
}
return left < right ? [left,right] : null;
}
扩展
由于第一道题还比较有意思,很有可能在工作中会用到,所以在这里进行封装和容错等一系列的改造,以达到拿来即用的效果。那我们如何进行优化呢?该从那几个点出发优化我们的代码呢?我们可以基于这道题目进行延伸:
我们的题目是统计当前页面中元素节点的数量总和、元素节点的最大嵌套深度以及最大子元素个数,我们从最简单的开始,先把这个函数改成拿来即用的工具函数,只要在script标签里面执行该函数就能返回这3个值,此时就要考虑当前执行环境,在node下没有html咋办,入参怎么设计,出参怎么设计。边界条件怎么考虑
?如果在遍历的过程中要你识别并统计标签?如果页面节点数太多,如何优化统计耗时?哪里执行比较耗时、代码执行时间怎么做上报,哪里可能会出错、执行出错怎么做上报?如果可以使用现有框架来提高开发效率,如何进行选择?