# 钉钉一面
# 笔试
有三个题目,使用 js 解决。
1. 实现一个函数,取交集并去重
intersection([1, 2, 2, 3, 4, 5], [3, 3, 4, 5, 6, 7]); // [3, 4, 5]
笔者的答案:
function intersection(a1, a2) {
return a1.filter((e) => a2.includes(e));
}
这题给的样例输入并不好,实现的函数里面没有体现出去重。 因此在后续的面试中,面试官要求给出一个去重的算法。
我给出了一个比较笨的方法:使用快慢指针进行搜索,如果有相同,则删除一个。
在 js 中可以直接通过 Set 进行去重
Array.from(new Set(array))
2. 解析字符串的 Query Params
parseQueryString("https://www.dingtalk.com/index.html?key0=0&key1=1&key2=2");
// output:
// {
// key0: "0",
// key1: "1",
// key2: "2",
// }
笔者的答案:
function parseQueryString(str) {
const q = str.split("?")[1];
if (!q) return {};
const obj = {};
for (const pair of q.split("&")) {
const [key, value] = pair.split("=");
obj[key] = value;
}
return obj;
}
这个答案可能并不是最好的,使用
reduce
函数构造对象可能更好,笔者现场没写出来。 参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
3. 将数组解析为树,然后输出树的深度和最大子节点数
let list = [
{ id: 1, name: "部门A", parentId: 0 },
{ id: 2, name: "部门B", parentId: 0 },
{ id: 3, name: "部门C", parentId: 1 },
{ id: 4, name: "部门D", parentId: 1 },
{ id: 5, name: "部门E", parentId: 2 },
{ id: 6, name: "部门F", parentId: 3 },
{ id: 7, name: "部门G", parentId: 2 },
{ id: 8, name: "部门H", parentId: 4 },
];
convert(list);
// output:
// [
// {
// "id": 1,
// "name": "部门A",
// "parentId": 0,
// "children": [
// {
// "id": 3,
// "name": "部门C",
// "parentId": 1,
// "children": [
// {
// "id": 6,
// "name": "部门F",
// "parentId": 3
// }
// ]
// },
// {
// "id": 4,
// "name": "部门D",
// "parentId": 1,
// "children": [
// {
// "id": 8,
// "name": "部门H",
// "parentId": 4
// }
// ]
// }
// ]
// },
// {
// "id": 2,
// "name": "部门B",
// "parentId": 0,
// "children": [
// {
// "id": 5,
// "name": "部门E",
// "parentId": 2
// },
// {
// "id": 7,
// "name": "部门G",
// "parentId": 2
// }
// ]
// }
// ]
calculate(tree);
// output:
// {
// maxDepth: 3,
// maxChildrenCount: 2,
// }
笔者的答案:
function convert(list) {
const tree = [];
for (const item of list) {
if (item.parentId === 0) {
tree.push(item);
}
for (const item2 of list) {
if (item.id === item2.parentId) {
if (!item.children) {
item.children = [];
}
item.children.push(item2);
}
}
}
return tree;
}
function calculate(tree) {
let maxDepth = 0;
let maxChildrenCount = 0;
function dfs(node, depth) {
maxDepth = maxDepth < depth ? depth : maxDepth;
if (node.children) {
maxChildrenCount =
maxChildrenCount < node.children.length
? node.children.length
: maxChildrenCount;
for (const child of node.children) {
dfs(child, depth + 1);
}
}
}
for (const node of tree) {
dfs(node, 1);
}
return { maxDepth, maxChildrenCount };
}
# 面试
面试通过电话进行,持续了约 56 分钟。以下为部分问题的摘录,其中顺序略有调整。
部分问题针对我个人,并没有普适性,所以没有摘录。
Q1: 在日常开发过程中是否使用过 ES6 特有的语法?
- 使用过,箭头函数可能是最常用的 ES6 特性。(解释有什么区别,下一问题)
- 使用 const 和 let 取代 var. (var 有什么问题,为什么不用 var)
- 可选链操作符和 '??' 符
- Symbol 作为新的一个数据类型(举例是 Vue 的 Project/inject 的 Key)
还有一些特性没有提到,比如对象的结构和 for of 循环。
箭头函数和普通函数有什么区别?
语法上,箭头函数比普通函数更加紧凑便利。最大的特点是箭头函数没有自己的 this。箭头函数的 this 是在定义时 获取的外部 this。是固定的。
有只能使用箭头函数的场景:防抖和节流函数的实现,(因为要涉及到 call(this), 不能使用普通函数定义)
var 有什么问题,为什么不推荐使用。
var 有一些历史遗留问题。var 的作用域只有全局作用域和函数作用域,因此在一些涉及到块作用域的情况下,有 var 的存在可能会造成问题。
使用 let 作为变量,则可以更好的使用作用域,避免很多问题的发生。
Q2:Vue 和 React 的区别
我认为最主要的区别是数据绑定的原理不同。 vue 基于 proxy 实现数据的双向绑定,因此 vue 作为一个 mvvm 框架, 数据改变则视图改变,视图改变同样会造成数据改变。
而 react 默认认为组件是无状态的,需要使用 useState 来定义状态。状态发生改变则对组件进行重新渲染。
还有一些其他的区别,比如 diff 算法的不同。 Vue 对比节点,如果类型相同而 className 不同,则认为是不同类型的元素 进行删除重建,但是 react 会认为是同类型的节点,只会修改节点属性。
vue 列表对比使用收尾指针法,而 react 采用从左到右依次对比的方法。
Q3 (没答上来): 是否了解过浏览器的重绘(reflow)和重排(repaint)
- HTML 被 HTML 解析器解析成 DOM 树;
- CSS 被 CSS 解析器解析成 CSSOM 树;
- 结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment;
- 生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点;
- 将布局绘制(paint)在屏幕上,显示出整个页面。
在页面的生命周期中,网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断触发重排(reflow)和重绘(repaint),不管页面发生了重绘还是重排,都会影响性能,最可怕的是重排,会使我们付出高额的性能代价,所以我们应尽量避免。
重绘不一定导致重排,但重排一定会导致重绘。
Q4(没答上来): Taro 或者是 uni-app 的编译机制
笔者只用过一次啊。。怎么知道这个! 丢一篇参考文章:
Q5 (答上来了吗) :Flutter 的跨平台是怎么实现的
Flutter 实际上也是 hybrid App,只不过性能比 React Native 或者是 Electron 这种要好。
还会问些啥
基本上除了上述的问题,还有就是看你简历里面写的项目经历,问一些相关的项目的具体问题。
还会问近期在学什么。