声明:关于以下面试题收集来源 (壹题)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
是可以更准确的,更快速的更新节点,而且不建议使用index
做 key
,因为每次索引都一样导致更新时候不会有任何效果
第 2 题:['1', '2', '3'].map(parseInt) what & why ?
刚看到这个题目大部分人跟我一样都会下意识的认为返回值应该是[1,2,3]
,好像哪里不对哈,我们一起一步一步分析来看
这个题主要考察的是 map
和 parseInt
传参
map
map()
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
var new_array = arr.map(function callback(currentValue[,index[, array]]) {
// Return element for new_array
}[, thisArg])
// currentValue :当前值
// index : 索引值
// array : callback map 方法被调用的数组
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);
};
}