🌞Moon Will Know
🔐

2022.11.22 面试记录

1.打印结果

for(var i = 0;i < 10 ;i++){ setTimeout(()=>{ console.log(i) },i*100) } for(var i = 0; i < 10; i++) { var j = i; setTimeout(()=>{ console.log(j) },i*100) }
这段代码的打印结果,如何让它递增打印。

解答

打印结果: 第一段代码会打印 10 个 10,第二段代码会打印 10 个 9,最后结果是轮流打印10,9。 这里涉及到 var 只存在函数作用域没有块级作用域,所以在 setTimeout 回调时拿到的 i 都已经变成了 10。 第二段代码同理,var 重复声明和赋值覆盖了 j,所以 setTimeout 回调时拿到的 i 都已经变成了 9。 优化: 这里的处理方式就是复制值或者生成块级作用域。
// 使用函数参数按值传递的特点 for(var i = 0;i < 10 ;i++){ (function(j) { setTimeout(()=>{ console.log(j) },i*100) })(i) } // 使用函数参数按值传递的特点 for(var i = 0;i < 10 ;i++){ setTimeout((j)=>{ console.log(j) } ,i*100 ,i) } // 使用 let 构建块级作用域 for(let i = 0;i < 10 ;i++){ setTimeout(()=>{ console.log(i) },i*100) }

反思

因为平时不怎么使用 var,所以对它的一些处理方式很生疏了。这里 setTimeout api 的第三个参数知识也不太清楚。

2. this 问题

class Foo { zoo = 100; bar() { setTimeout(this.baz) } baz() { console.log(this.zoo) } } new Foo().bar();

解答

打印结果:undefined 这里几乎直觉就是 undefined, 但是问题是在没理清楚思路。还是函数的参数是按值或按引用传递的,这里传递函数被作为 setTimeout 的 callback 调用时,就是直接调用 bar 的引用了,所以 this 指向 globalThis,值为 undefined

反思

还是对回调的本质不是太熟悉,思考很久还要被引导才能想出来。另外就是平时写习惯了箭头函数包裹一层,这个题比较反直觉。但思考这么久还是不应该。

3. CSS 优先级

! imporant > 行内式 > id选择器 > 类选择器 > 标签选择器

4. 如何覆盖子组件样式

  1. 传递 style 属性
  1. 传递 className 属性
  1. 组件属性定义在一个 @layer 中,外部在全局写一个新的 @layer 覆盖,这样写比直接在全局写的好处是不会污染其他的组件,但是坏处写组件时也要按 @layer 规则来书写。

5. render 方法在什么时候会执行

解答

  1. 组件初次渲染时
  1. 组件内部 state 发生变化时
  1. 父组件 render() 方法执行时
  1. 主动执行 froceUpdate() 方法

反思

这里后面两种没说出来,本来是想说 froceUpdate() 的,但是本身对这个方法不熟悉,而且当时脑子里想的是 foucsUpadate()

6. React 避免重复渲染的方法

  1. React.pureComponent ,这个组件内部实现了 componentShouldUpdate() 生命周期,会对组件的 props 和 state 进行浅比较。
  1. React.memo 高阶组件,这个组件返回一个新的组件,会对组件的 props 进行浅比较。
  1. componentShouldUpdate() 生命周期,手动比较 props 和 state 的变化,如果返回false,则会跳过组件 render 方法。
  1. React.useMemo 钩子,在钩子的回调中返回一个组件,并传递依赖项。
  1. React.useCallback 钩子,如果子组件的 props 只传递了 props,那么使用 React.useCallback 缓存会减少重复渲染。另外使用 React.memo 时也需要配合 React.useCallback 来使用。
  1. Reduxdispatch ,当子组件只调用方法时,可以将方法抽离到 dispatch 中,只传递 dispatch 方法,这个方法不会更新。

反思

后两种没说出来,之前总结的部分也没总结这两种,刚写的时候才想出来。剩下可能还有些代码规范类比如 避免使用 flushSync()foucsUpadate() ,升级下 React 到新版本拥抱合并更新特性等等。

7. 动画的属性,写一个 App 删除队列动画

解答

// 定义动画 @keyframe animate { // 定义关键帧 0% { } 100 % { } } .box { animation: 3s ease-in 1s 2 reverse both paused animate; // 时间 时间函数 延迟 执行次数 方向 执行前和执行后的显示方法 动画运行状态 动画名 }
写一个 App 删除队列动画 因为前一个问题回答的时关于 CSS 动画的,所以钻牛角尖了。以为是要用 CSS 来实现,实在想不出来,但是后面觉得应该问的是可以用 JS 的。 思路:
  1. 删除前拿到所有后面兄弟元素的位置,然后计算他们的目标位置。
  1. 使用 JS 操作给他们加上 transform 属性移动他们的位置。
  1. 移动过程中为删除元素执行删除动画,但是并没有真的删除。
  1. 删除和移动动画执行完毕时,删除元素。
  1. 这里考虑到优化,动画的连续执行应使用 requestAnimationFrame 来合并关键帧上的 Dom 操作。
我自己来写的话没有特殊要求就只用 motion 上了,我的博客中的筛选动画 就是通过这个来实现的。

8. asyncawait 相关

解答

asyncawait 的作用 async 包装的函数内部和普通函数无异,但是返回体会变成 promise。await 只能在 await 内部使用,如果后面的语句是一个 promise 那就等待 promise 的结果,如果不是 promise 那就将其包装为一个 promise,并等待和返回他的结果。如果 promise 的状态变为 rejected,则抛出一个错误。
async 函数内部如何等待一系列值的结果 使用 Promise.allPromise.allSettled
async 函数内部如何按顺序等待 按顺序写 await
async 函数内的错误处理try...catch

反思

这里其实可以引到 asyncawait 的本质 生成器上去,但是当时不敢说。

9. 手写深拷贝

深拷贝一个 JSON 序列化对象,即不用考虑 Symbol 类型,循环引用 与相互引用和原型的问题。

解答

function getType(obj) { return Object.prototype.toString.call(obj).slice(8, -1); } function cloneDeep(obj) { let result; const type = getType(obj); if (type === "Object") { result = {}; } else if (type === "Array") { result = []; } else { return obj; } for (let k in obj) { if(getType(obj[k]) === 'Object' || getType(obj[k]) === 'Array') { result[k] = cloneDeep(obj[k]); } result[k] = obj[k]; } return result; }

反思

第一次遇到手写代码的面试题,回来之后发现 slice 果然写错了。代码这里也没有简化,里面有些重复判断的问题。

总结

整体来说这个面试问了很多基础性的知识,其实自己准备 JS 基础这块知识还是挺多的,但是经常太直接性思考了,遇到反直觉的简单的东西就很难变通。 另外一个问题就是紧张,其实从上午开始就一直心跳加速,本来以为有关作用域和 this 都会问写概念性的东西,但是上来就看代码,感觉看着屏幕近视都加重了几度,这里还是应该多锻炼心态。 总之是一场很简单很基础的面试,答得不是很理想,希望能重视些简单的问题和锻炼心态吧。