跳到主要内容

前端大厂面试问答[1]

· 阅读需 7 分钟

声明:关于以下面试题收集来源 (壹题)https://github.com/Advanced-Frontend/Daily-Interview-Question/blob/master/datum/summary.md 答案综合了自己理解如有误区请帮忙指出,谢谢 😆

第 1 题:(滴滴、饿了么)写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

首先可以知道React/Vue都是基于 Vnode(虚拟节点)采用 diff 算法进行更新,通过 key 值绑定可以更快速,更准确拿到 oldVnode 中对应的 vnode 节点,提高 diff 效率。

vue 部分源码解析

// vue 项目 src/core/vdom/patch.js -488 行
// 以下是为了阅读性进行格式化后的代码

// oldCh 是一个旧虚拟节点数组
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
if (isDef(newStartVnode.key)) {
// map 方式获取
idxInOld = oldKeyToIdx[newStartVnode.key];
} else {
// 遍历方式获取
idxInOld = 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;
}
}

相对遍历查找 map 映射理论上更快速尤其在对比较大的数据量的时候效果就很明显了,还有一个就是数据更新时组件的强制更新, 以避免“原地复用”带来的副作用。

总结一句就是,key 是可以更准确的,更快速的更新节点,而且不建议使用indexkey,因为每次索引都一样导致更新时候不会有任何效果

第 2 题:['1', '2', '3'].map(parseInt) what & why ?

刚看到这个题目大部分人跟我一样都会下意识的认为返回值应该是[1,2,3],好像哪里不对哈,我们一起一步一步分析来看 这个题主要考察的是 mapparseInt 传参

map

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

var new_array = arr.map(function callback(currentValue[,index[, array]]) {
// Return element for new_array
}[, thisArg])
// currentValue :当前值
// index : 索引值
// array : callback map 方法被调用的数组

MDN

parseInt

parseInt() 函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。

const intValue = parseInt(string[, radix]);
// string : 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。
// radix : 一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。默认为10。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
// 返回值 : 返回一个整数或NaN

MDN 温习了基础知识后简单分析一下,首先 map 函数传了 parseInt 作为回调函数,那么 map 的相关参数也就直接可以传入 parseInt 函数中,map 的 currentValue 作为parseInt 要被解析的字符串传入,第二个当前值的索引被当做radix 进制数传入,第三个参数忽略。 map 遍历大概执行了下面 3 个逻辑

parseInt('1', 0);
parseInt('2', 1);
parseInt('3', 2);

注意: 在 radix 为 undefined,或者 radix 为 0 或者没有指定的情况下,JavaScript 作如下处理:

  • 如果字符串 string 以"0x"或者"0X"开头, 则基数是 16 (16 进制).
  • 如果字符串 string 以"0"开头, 基数是 8(八进制)或者 10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用 10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出 radix 参数的值。
  • 如果字符串 string 以其它任何值开头,则基数是 10 (十进制)。`

结论 第一个传入基数为 0 并且字符串没有以 0 或者 0x 开头,默认取 10 进制,那么返回值就是Number 1 第二个传入基数为 1 无效,直接返回 NaN 第三个传入基数为 2,那么就是以 2 进制运算解析,但是二进制中只能为1 or 0,传入 3 为非法值,返回 NaN 返回结果 [1,NaN,NaN] 如何按照我们预期返回对应Number类型?

['1', '2', '3'].map(num => parseInt(num, 10));

第 3 题:(挖财)什么是防抖和节流?有什么区别?如何实现?

防抖

触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间,简单来说就是避免事件频繁被调用

思路 每次触发高频操作后取消之前的延时调用

function debounce(fn) {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, 500);
};
}
function doSomething() {
console.log('防抖成功了!');
}
const inp = document.querySelector('#inp');
inp.addEventListener('input', debounce(doSomething)); // 防抖

节流

指定时间段内只允许程序调用一次

思路 每次调用之前判断是否超时,如未超时拒绝执行等待下次调用

function throttle(fu, time = 500) {
let timer = null;
return function() {
if (timer) return;
setTimeout(() => {
fn.apply(this, arguments);
clearTimeout(timer);
timer = null;
}, time);
};
}