包含JS、CSS、React、浏览器等 前端经典面试题( 六 )


把 DOM 离线后修改,比如:先把 DOM 给 display:none(有一次 Reflow),然后你修改100次,然后再把它显示出来
不要把 DOM 结点的属性值放在一个循环里当成循环里的变量for(let i = 0; i < 1000; i++) {// 获取 offsetTop 会导致回流,因为需要去获取正确的值console.log(document.querySelector('.test').style.offsetTop)}

  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  • CSS 选择符从右往左匹配查找,避免 DOM 深度过深
  • 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素 。比如对于 video标签,浏览器会自动将该节点变为图层 。
  • react、Vue1、写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?参考答案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)}if(isDef(newStartVnode.key)) {// map 方式获取idxInOld = oldKeyToIdx[newStartVnode.key]} else {// 遍历方式获取idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)}创建map函数
    function createKeyToOldIdx (children, beginIdx, endIdx) {let i, keyconst map = {}for (i = beginIdx; i <= endIdx; ++i) {key = children[i].keyif (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、React 中 setState 什么时候是同步的,什么时候是异步的?参考答案在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state 。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用 。
    **原因:**在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state 。
    3、下面输出什么class Example extends React.Component {constructor() {super();this.state = {val: 0};}componentDidMount() {this.setState({val: this.state.val + 1});console.log(this.state.val);// 第 1 次 logthis.setState({val: this.state.val + 1});console.log(this.state.val);// 第 2 次 logsetTimeout(() => {this.setState({val: this.state.val + 1});console.log(this.state.val);// 第 3 次 logthis.setState({val: this.state.val + 1});console.log(this.state.val);// 第 4 次 log}, 0);}render() {return null;}};1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0 。2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次 。设置完成后 state.val 值为 1 。3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3 。输出: 0 0 2 34、为什么虚拟dom会提高性能?参考答案虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能 。
    具体实现步骤如下:
    用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中


    推荐阅读