面试题记录-2024

Sep 25, 2024

#面试题

面试篇

一、try...catch只能捕捉到同步执行代码块中的错误

例子

1、异步方法情况

javascript
try { setTimeout(() => { throw new Error('err') }, 200); } catch (err) { console.log(err); } <--正确方案--> new Promise((resolve, reject) => { setTimeout(() => { try { throw new Error('err'); } catch (err) { reject(err); } }, 200); }) .then(() => { // 正常执行时的处理逻辑 }) .catch((err) => { console.log(err); // 这里会捕捉到错误 });

2、promise链下

javascript
try { Promise.resolve().then(() => { throw new Error('err') }) } catch (err) { console.log(err); } <--正确方案--> // 方法一 Promise.resolve() .then(() => { throw new Error('err'); }) .catch((err) => { console.log(err); // 这里会捕捉到错误 }); // 方法二 async function handleError() { try { await Promise.resolve().then(() => { throw new Error('err'); }); } catch (err) { console.log(err); // 这里会捕捉到错误 } } handleError();

二、在 Vue2.x 中如何检测数组的变化

使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。

三、vue2、vue3 diff算法

Vue.js 是一个流行的 JavaScript 框架,用于构建用户界面。在 Vue 的两个主要版本(Vue 2.x 和 Vue 3.x)中,虚拟 DOM(Virtual DOM)和其 diff 算法是非常重要的特性。diff 算法用于高效地更新 DOM,保持界面和状态的一致。下面我们来详细探讨一下这两个版本中的 diff 算法。

Vue 2.x 的 diff 算法

Vue 2.x 使用的是基于 Snabbdom 的 diff 算法,主要特点如下:

  1. 双端比较:从新旧节点的两端开始进行比较(头头、头尾、尾头、尾尾),尽可能快地找到不需要移动的节点。

![image-20240617152232885](/Users/lizehang/Library/Application Support/typora-user-images/image-20240617152232885.png)

  1. 标记静态节点:在编译阶段,Vue 2.x 会尝试标记出静态节点,这样在更新过程中可以跳过这些不变的节点,从而提高性能。
  2. SameNode 函数:判断两个节点是否相同的核心函数。节点相同的条件包括:
    • key 相同
    • 标签相同
    • 都是注释节点
    • 都是文本节点并且内容相同
  3. 递归更新:当两个节点被判断为相同时,进行递归更新。对于组件节点,会调用组件的钩子函数进行处理。

具体的算法步骤可以概述为:

  • 比较两个节点,如果节点不同,则直接替换。
  • 如果节点相同,则递归比较子节点。
  • 使用双端比较法减少移动节点的次数。

Vue 3.x 的 diff 算法

Vue 3.x 进行了大量的重构,引入了全新的编译器和运行时,其中 diff 算法也得到了显著改进。主要特点如下:

  1. 块级优化:在编译阶段,Vue 3.x 会将节点划分为静态和动态的“块”,并使用静态标记(patchFlag)来标记哪些部分需要更新,从而减少不必要的比较。
  2. 缓存节点:Vue 3.x 会缓存一些节点的状态,从而避免不必要的重新创建和销毁。
  3. 更高效的 SameNode 函数:进一步优化了 SameNode 函数的逻辑,使得比较更为高效。
  4. 最长递增子序列:使用了最长递增子序列(Longest Increasing Subsequence, LIS)算法来优化节点的移动操作,这样可以在节点移动时,最大限度地减少实际 DOM 操作。

具体的算法步骤可以概述为:

  • 比较根节点,如果根节点不同,则直接替换。
  • 如果根节点相同,则根据静态标记(patchFlag)和节点类型(组件、文本、元素等)进行不同的处理。
  • 对于子节点,使用最长递增子序列算法减少移动操作。
  • 根据缓存的状态和静态标记,跳过不必要的比较和更新。

举例说明

假设有如下虚拟 DOM 结构:

html
<ul> <li key="a">A</li> <li key="b">B</li> <li key="c">C</li> <li key="d">D</li> </ul>

我们将其更新为:

html
<ul> <li key="b">B</li> <li key="a">A</li> <li key="d">D</li> <li key="c">C</li> </ul>

Vue 2.x

Vue 2.x 的算法会逐一比较每个节点:

  1. 比较 <li key="a"><li key="b">,不同。
  2. 比较 <li key="d"><li key="c">,不同。
  3. 最终需要移动和重新插入节点。

Vue 3.x

Vue 3.x 的算法会利用静态标记和最长递增子序列算法:

  1. 比较根节点 <ul>,相同。
  2. 比较子节点,发现 <li key="b"><li key="a"> 需要交换位置,但不需要重新创建。
  3. 利用最长递增子序列算法,确定最少的 DOM 操作次数。

总结

Vue 2.x 和 Vue 3.x 的 diff 算法在基本原理上相似,但 Vue 3.x 进行了更深入的优化,特别是在静态标记和节点移动方面,大大提高了性能和效率。这使得 Vue 3.x 在处理大型应用时,能够更加高效地更新 DOM,提升用户体验。

四、数组去重

1、原始类型

new Set

2、引用类型

根据对象某个key去重 [...new Map(arr.map(i=>[i.key,i])).values]

目录