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. 如何覆盖子组件样式
- 传递 style 属性
- 传递 className 属性
- 组件属性定义在一个
@layer
中,外部在全局写一个新的@layer
覆盖,这样写比直接在全局写的好处是不会污染其他的组件,但是坏处写组件时也要按@layer
规则来书写。
5. render 方法在什么时候会执行
解答
- 组件初次渲染时
- 组件内部
state
发生变化时
- 父组件
render()
方法执行时
- 主动执行
froceUpdate()
方法
反思
这里后面两种没说出来,本来是想说
froceUpdate()
的,但是本身对这个方法不熟悉,而且当时脑子里想的是 foucsUpadate()
。6. React 避免重复渲染的方法
React.pureComponent
,这个组件内部实现了componentShouldUpdate()
生命周期,会对组件的 props 和 state 进行浅比较。
React.memo
高阶组件,这个组件返回一个新的组件,会对组件的 props 进行浅比较。
componentShouldUpdate()
生命周期,手动比较 props 和 state 的变化,如果返回false
,则会跳过组件render
方法。
React.useMemo
钩子,在钩子的回调中返回一个组件,并传递依赖项。
React.useCallback
钩子,如果子组件的 props 只传递了 props,那么使用React.useCallback
缓存会减少重复渲染。另外使用React.memo
时也需要配合React.useCallback
来使用。
Redux
的dispatch
,当子组件只调用方法时,可以将方法抽离到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 的。
思路:
- 删除前拿到所有后面兄弟元素的位置,然后计算他们的目标位置。
- 使用 JS 操作给他们加上
transform
属性移动他们的位置。
- 移动过程中为删除元素执行删除动画,但是并没有真的删除。
- 删除和移动动画执行完毕时,删除元素。
- 这里考虑到优化,动画的连续执行应使用
requestAnimationFrame
来合并关键帧上的 Dom 操作。
我自己来写的话没有特殊要求就只用 motion 上了,我的博客中的筛选动画 就是通过这个来实现的。
8. async
和 await
相关
解答
async
和 await
的作用
async
包装的函数内部和普通函数无异,但是返回体会变成 promise。await
只能在 await
内部使用,如果后面的语句是一个 promise 那就等待 promise 的结果,如果不是 promise 那就将其包装为一个 promise,并等待和返回他的结果。如果 promise 的状态变为 rejected
,则抛出一个错误。async
函数内部如何等待一系列值的结果
使用 Promise.all
或 Promise.allSettled
async
函数内部如何按顺序等待
按顺序写 await
async
函数内的错误处理
写 try...catch
反思
这里其实可以引到
async
和 await
的本质 生成器上去,但是当时不敢说。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 都会问写概念性的东西,但是上来就看代码,感觉看着屏幕近视都加重了几度,这里还是应该多锻炼心态。
总之是一场很简单很基础的面试,答得不是很理想,希望能重视些简单的问题和锻炼心态吧。