⾼级前端程序员⾯试问题与答案【精选9道】
1. 写 React/Vue 项⽬时为什么要在组件中写 key,其作⽤是什么?
key 的作⽤是为了在 diff 算法执⾏时更快的到对应的节点,提⾼ diff 速度。
vue 和 react 都是采⽤ diff 算法来对⽐新旧虚拟节点,从⽽更新节点。在 vue 的 diff 函数中。可以先了解⼀下 diff 算法。
在交叉对⽐的时候,当新节点跟旧节点头尾交叉对⽐没有结果的时候,会根据新节点的 key 去对⽐旧节点数组中的 key,从⽽到相应旧节点(这⾥对应的是⼀个 key => index 的 map 映射)。如果没到就认为是⼀个新增节点。⽽如果没有 key,那么就会采⽤⼀种遍历查的⽅式去到对应的旧节点。⼀种⼀个 map 映射,另⼀种是遍历查。相⽐⽽⾔。map 映射的速度更快。
vue 部分源码如下:
// vue 项⽬ src/core/vdom/patch.js -488 ⾏
// oldCh 是⼀个旧虚拟节点数组,
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
创建 map 函数:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
遍历寻:
/
/ sameVnode 是对⽐新旧节点是否相同的函数
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
2. 解析 [‘1’, ‘2’, ‘3’].map(parseInt)
第⼀眼看到这个题⽬的时候,脑海跳出的答案是 [1, 2, 3],但是真正的答案是 [1, NaN, NaN]。
⾸先让我们回顾⼀下,map 函数的第⼀个参数 callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
这个 callback ⼀共可以接收三个参数,其中第⼀个参数代表当前被处理的元素,⽽第⼆个参数代表该元素的索引。
⽽ parseInt 则是⽤来解析字符串的,使字符串成为指定基数的整数。
parseInt(string, radix)接收两个参数,第⼀个表⽰被处理的值(字符串),第⼆个表⽰为解析时的基数。
了解这两个函数后,我们可以模拟⼀下运⾏情况
1. parseInt(‘1’, 0) //radix 为 0 时,且 string 参数不以“0x”和“0”开头时,按照 10 为基数处理。这个时候返回 1;
2. parseInt(‘2’, 1) // 基数为 1(1 进制)表⽰的数中,最⼤值⼩于 2,所以⽆法解析,返回 NaN;
3. parseInt(‘3’, 2) // 基数为 2(2 进制)表⽰的数中,最⼤值⼩于 3,所以⽆法解析,返回 NaN。
map 函数返回的是⼀个数组,所以最后结果为 [1, NaN, NaN]。
最后附上 MDN 上对于这两个函数的链接,具体参数⼤家可以到⾥⾯看:
3. 什么是防抖和节流?有什么区别?如何实现?
防抖
触发⾼频事件后 n 秒内函数只会执⾏⼀次,如果 n 秒内⾼频事件再次被触发,则重新计算时间;
思路:
每次触发事件时都取消之前的延时调⽤⽅法:
function debounce(fn) {
let timeout = null; // 创建⼀个标记⽤来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当⽤户输⼊的时候把前⼀个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后⼜创建⼀个新的 setTimeout, 这样就能保证输⼊字符后的 interval 间隔内如果还有字符输⼊的话,就不会执⾏ fn 函数 fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = ElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
2. 节流
⾼频事件触发,但在 n 秒内只会执⾏⼀次,所以节流会稀释函数的执⾏频率。
思路:
每次触发事件时都判断当前是否有等待执⾏的延时函数。
function throttle(fn) {
let canRun = true; // 通过闭包保存⼀个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
canRun = false; // ⽴即设置为 false
setTimeout(() => { // 将外部传⼊的函数的执⾏放在 setTimeout 中
fn.apply(this, arguments);
// 最后在 setTimeout 执⾏完毕后再把标记设置为 true(关键) 表⽰可以执⾏下⼀次循环了。当定时器没有执⾏的时候标记永远是 false,在开头被 return 掉 canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
4. 介绍下 Set、Map、WeakSet 和 WeakMap 的区别?
Set
成员唯⼀、⽆序且不重复;
[value, value],键值与键名是⼀致的(或者说只有键值,没有键名);
可以遍历,⽅法有:add、delete、has。
WeakSet
成员都是对象;
成员都是弱引⽤,可以被垃圾回收机制回收,可以⽤来保存 DOM 节点,不容易造成内存泄漏;
不能遍历,⽅法有 add、delete、has。
Map
本质上是键值对的集合,类似集合;
可以遍历,⽅法很多,可以跟各种数据格式转换。
WeakMap
只接受对象最为键名(null 除外),不接受其他类型的值作为键名;
键名是弱引⽤,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是⽆效的;
不能遍历,⽅法有 get、set、has、delete。
5. 介绍下深度优先遍历和⼴度优先遍历,如何实现?
深度优先遍历(DFS)
深度优先遍历(Depth-First-Search),是搜索算法的⼀种,它沿着树的深度遍历树的节点,尽可能深地搜索树的分⽀。当节点 v 的所有边都已被探寻过,将回溯到发现节点 v 的那条边的起始节点。这⼀过程⼀直进⾏到已探寻源节点到其他所有节点为⽌,如果还有未被发现的节点,则选择其中⼀个未被发现的节点为源节点并重复以上操作,直到所有节点都被探寻完成。
简单的说,DFS 就是从图中的⼀个节点开始追溯,直到最后⼀个节点,然后回溯,继续追溯下⼀条路径,直到到达所有的节点,如此往复,直到没有路径为⽌。
DFS 可以产⽣相应图的拓扑排序表,利⽤拓扑排序表可以解决很多问题,例如最⼤路径问题。⼀般⽤堆数据结构来辅助实现 DFS 算法。
注意:深度 DFS 属于盲⽬搜索,⽆法保证搜索到的路径为最短路径,也不是在搜索特定的路径,⽽是通过搜索来查看图中有哪些路径可以选择。
步骤:
访问顶点 v;
依次从 v 的未被访问的邻接点出发,对图进⾏深度优先遍历;直⾄图中和 v 有路径相通的顶点都被访问;
若此时途中尚有顶点未被访问,则从⼀个未被访问的顶点出发,重新进⾏深度优先遍历,直到所有顶点均被访问过为⽌。
实现:
Graph.prototype.dfs = function() {
var marked = []
for (var i=0; i<this.vertices.length; i++) {
if (!marked[this.vertices[i]]) {
dfsVisit(this.vertices[i])
}
}
function dfsVisit(u) {黑马程序员前端全套视频
let edges = this.edges
marked[u] = true
console.log(u)
var neighbors = (u)
for (var i=0; i<neighbors.length; i++) {
var w = neighbors[i]
if (!marked[w]) {
dfsVisit(w)
}
}
}
}
测试:
graph.dfs()
// 1
// 4
// 3
// 2
// 5
测试成功。
⼴度优先遍历(BFS)
⼴度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终⽌,BFS 同样属于盲⽬搜索,⼀般⽤队列数据结构来辅助实现 BFS。
BFS 从⼀个节点开始,尝试访问尽可能靠近它的⽬标节点。本质上这种遍历在图上是逐层移动的,⾸先检查最靠近第⼀个节点的层,再逐渐向下移动到离起始节点最远的层。
步骤:
创建⼀个队列,并将开始节点放⼊队列中;
若队列⾮空,则从队列中取出第⼀个节点,并检测它是否为⽬标节点;
若是⽬标节点,则结束搜寻,并返回结果;
若不是,则将它所有没有被检测过的字节点都加⼊队列中;
若队列为空,表⽰图中并没有⽬标节点,则结束遍历。
实现:
Graph.prototype.bfs = function(v) {
var queue = [], marked = []
marked[v] = true
queue.push(v) // 添加到队尾
while(queue.length > 0) {
var s = queue.shift() // 从队⾸移除
if (this.edges.has(s)) {
console.log('visited vertex: ', s)
}
let neighbors = (s)
for(let i=0;i<neighbors.length;i++) {
var w = neighbors[i]
if (!marked[w]) {
marked[w] = true
queue.push(w)
}
}
}
}
测试:
graph.bfs(1)
// visited vertex: 1
// visited vertex: 4
// visited vertex: 3
// visited vertex: 2
/
/ visited vertex: 5
测试成功。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论