刷面试题相关知识点的整理(持续更新ing...)

188 阅读7分钟

一.写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

key是给每一个vnode的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点

1. 更准确

因为带key就不是就地复用(默认模式)了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。

2. 更快

利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。)

vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中(建议先了解一下diff算法过程)。
在交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。

评论总结如下:

1.key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度。

2.就我的使用来说(Vue)key的作用是为了在数据变化时强制更新组件,以避免“原地复用”带来的副作用。另外,某些情况下不带key可能性能更好。

3.主要是为了提升diff【同级比较】的效率。自己想一下自己要实现前后列表的diff,如果对列表的每一项增加一个key,即唯一索引,那就可以很清楚的知道两个列表谁少了谁没变。而如果不加key的话,就只能一个个对比了。

4.就react而言,key是对于列表组件而言,并且无key或者key不唯一会报错提示

5.说到底,key的作用就是更新组件时判断两个节点是否相同。相同就复用,不相同就删除旧的创建新的。如不带key,只适用于渲染简单的无状态组件,是刻意依赖默认行为(原地复用)以获取性能上的提升。对于大多数场景来说,列表组件都有自己的状态。

6.我的理解是,vue和react虽然都采用了diff算法。 但是react本身的设计和vue的设计是截然不同的, vue采用了更加细粒度的更新组件的方式,即给每一个属性绑定监听, 而react是采用自顶而下的更新策略,每次小的改动都会生成一个全新的vdom。从而进行diff,如果不写key,可能就会发生本来应该更新却没有更新的bug。这个bug其实和diff算法有关,react团队完全可以写一个没有这个“bug”版本的代码, 但是这是一种权衡,一种性能和方便使用的权衡。 写不写key能够提高性能的根本在于一方面diff算法会优先判断key是否相同,如果相同则不进行后面的运算。 如果key相同,就更好了,根本不需要重新创建节点,总结, 更确切的说应该是diff算法在你的复杂的列表稳定的时候能够明显提高性能,因为节点可以重用。但是对于列表频繁更新的场景, 节点不能重用,但是diff 可以省略一部分逻辑,因此性能也会更好。但是两者的性能优化不在同一个纬度,一个是 创建和更新节点(我称之为渲染器)的优化,一个是DOM diff 算法(我称之为核心引擎)的优化。

7.如下:

  • vue官方文档中说的“刻意依赖默认行为以获取性能上的提升”,是因为在不带key的情况下,节点能够复用,省去了销毁/创建组件的开销,同时只需要修改DOM文本内容而不是移除/添加节点。但是这种只能用在渲染比较简单的无状态组件,否则会产生副作用。

  • 而对于大多数场景来说,列表组件都有自己的状态(如进出列表的动画效果)。所以为了在数据变化时强制更新组件,以避免‘原地复用’带来的副作用。

  • 提升diff算法【同级比较】的效率,当有唯一key存在时,在第一步判断sameVnode函数中加快判断节点是否相同,不同则替换节点,而减少通过patchVnode函数去挨个判断,以及递归判断子节点。diff算法中不足点是当父节点不同时,就直接替换整个父节点,而如果此时子节点是相同的,也将被替换,无法复用

终点总结:

不用 key:

  • 就地复用节点。在比较新旧两个节点是否是同一个节点的过程中会判断成新旧两个节点是同一个节点,因为 a.key 和 b.key 都是 undefined。所以不会重新创建节点和删除节点,只会在节点的属性层面上进行比较和更新。所以可能在某种程度上(创建和删除节点方面)会有渲染性能上的提升;

  • 无法维持组件的状态。由于就地复用节点的关系,可能在维持组件状态方面会导致不可预知的错误,比如无法维持改组件的动画效果、开关等状态;

  • 无法维持组件的状态。由于就地复用节点的关系,可能在维持组件状态方面会导致不可预知的错误,比如无法维持改组件的动画效果、开关等状态;

用 key:

  • 维持组件的状态,保证组件的复用。因为有 key 唯一标识了组件,不会在每次比较新旧两个节点是否是同一个节点的时候直接判断为同一个节点,而是会继续在接下来的节点中找到 key 相同的节点去比较,能找到相同的 key 的话就复用节点,不能找到的话就增加或者删除节点。

  • 查找性能上的提升。有 key 的时候,会生成 hash,这样在查找的时候就是 hash 查找了,基本上就是 O(1) 的复杂度。

  • 节点复用带来的性能提升。因为有 key 唯一标识了组件,所以会尽可能多的对组件进行复用(尽管组件顺序不同),那么创建和删除节点数量就会变少,这方面的消耗就会下降,带来性能的提升。

总结:性能提升不能只考虑一方面,不是 diff 快了性能就快,不是增删节点少了性能就快,不考虑量级的去评价性能,都只是泛泛而谈。

二.["1","2","3"].map(parseInt) what & why ?

输出结果:[1, NaN, NaN]

**原因:**parseInt() 函数可解析一个字符串,并返回一个整数,parseInt(string, radix)含有两个参数,string要被解析的字符串,是必须的,radix可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。当参数 基数radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数radix。

  • 当string 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数;

  • 当string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字;

  • string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数;

上题目分析如下: