九号公司 前端一面面经

401 阅读9分钟

面试官人很好,沟通很愉快;问了很多问题,大多数是回答上来了,但是总结的时候发现我的回答很简陋,也有不少缺陷,也不够系统,整理如下

最大的问题是————知识不够系统,知道一些,但是不是那么熟悉,因此表达欠佳。还是需要系统的整理和复习

实习期间做的事情

Promise解决了哪些痛点

避免了回调地狱

什么是回调地狱

把"回调地狱"理解成大量的函数嵌套和大量的缩进似乎有欠妥当,因为我们大可以把这些代码抽离出来,这在我们写代码的过程中其实是一件很常见的事情。

“回调地狱”所说的嵌套其实是指异步的嵌套。它带来了两个问题:可读性的问题信任问题

可读性问题:

异步任务在阅读起来本来就比较吃力,因为这些任务有的是同步的,有的是异步的,我们需要随时想着这些任务的执行顺序,随着任务复杂度提高,代码阅读难度几何倍数增长。

信任问题:

传统的回调函数可能会出现以下问题

  • 回调函数被回调过早(一般是异步任务被同步调用);
  • 回调函数没有被调用或过晚被调用;
  • 回调次数过多;

使用回调函数的方式,如果我们使用了第三方的库并传入了一个回调函数,回调的调用时机对我们来说都是透明的,这样很危险,这个第三方库对我们来说是不可信任的。我们给他提供的回调函数相当于一种依赖注入,发生了控制翻转,这在模块解耦中很有用,但是不应该用在这里。

Promise的解决原理

Promise解决可读性问题

Promise一定是异步的,这样的话代码就会清晰很多。

Promise解决信任问题

实际上Promise也不是没有发生控制翻转,只是通过resolve和reject翻转了控制翻转,将控制权又交回给了程序员。在Promise中,回调只用于通知成功或者失败。

  • 次数过多:Promise的状态只能被改变一次,then方法的回调也只能执行一次,这就解决了回调次数的问题。
  • 回调过早:Promise一定是一个异步任务。
  • 没有被调用:Promise本身一定会调用,但是由于网络原因引起的回调过晚无法解决,但是可以用Promise.race()实现超时时间

参考文章:Promise到底解决了什么问题?听起来高大上的控制反转(IOC)是什么?

Promise应用场景

Promise.all():将多个请求合并在一起

Promise.race():处理超时

Promise.then():下一个请求依赖于上一个请求、处理中间数据

awit后面跟一个同步函数会怎么样

会将后面的数据用Promise.resolve()包装一下,依然会阻塞后面的代码

React和Vue的区别

写法不同:

react使用jsx,all in js,vue使用模板,all in vue,各有优势,现在vue也支持jsx了。

响应式不同:

vue使用数据代理,自动处理;react需要手动设置;这是设计理念的不同,没有优劣;react推崇数据不可变、单向数据流,而Vue专攻上手简单和依赖收集。

更新方式不同:

vue会自动追踪依赖关系,只更新必要的部分;react会自顶向下全部diff,需要手动用shouldComponentUpdate做逻辑判断。但是react有fiber可以很好的提高用户体验。

diff算法有所不同:

react因为其fiber结构是链表的原因,无法使用双端diff,但是vue是数组,使用双端diff效率较高。react对那种节点被后移的操作不敏感,会导致很多多余的dom操作。

闭包的应用

  1. 在es6之前模仿块级作用域
  2. 柯里化:把先前传入的参数用闭包保存起来
  3. 创建一个私有空间,保护内部的变量
  4. 缓存:如果函数调用处理耗时,我们可以将结果在内存中缓存起来,下次执行时,若存在内存中,则直接返回

面试官说了防抖节流:二者区别

  • 防抖:用户连续操作后延迟执行回调
  • 节流:连续触发某个事件,在一定时间内只执行一次

V8垃圾回收机制

内存空间氛围新生代和老生代,使用不同的算法

新生代:这部分的回收比较频繁,使用Scavenge算法,对象一开始进入这个区域,新生代内存被分为两个空间,叫做From和To,From存储数据To为闲置状态,开始垃圾回收时,会先会检查From空间中存活的对象,将所有存活对象复制到To空间中,剩下的就是非存活的变量,直接清空,然后调换两个空间的角色等待下一次回收。

对象晋升,当新生代内存不足时,就会把一部分存活时间比较久的对象复制到老生代,以下是两个条件

  • 对象是否经历过一次Scavenge算法
  • To空间的内存占比是否已经超过25%

老生代:这部分空间较小,回收也没有那么频繁,使用的算法也不同,是通过 Mark-Sweep(标记清除)Mark-Compact(标记整理)两个算法实现的,前一个就是从根对象开始遍历,删掉无法遍历到的对象的那个算法,但是由于这样做会产生内存碎片,所以又用标记整理来清除碎片,就是把所有存活的对象移到一端,然后剩下的空间直接清除

参考文章:V8引擎垃圾回收原理解析

Hooks的优点,为什么使用Hooks

什么是hooks,译作钩子。

react中用use开头的一系列函数,使得函数组件能够使用state和effect

vue3中使用组合式API,提供了组件复用、状态管理等开发能力的方法。

为什么使用hooks:

  1. 更好的状态复用

    • 在没有hooks的时候,react和vue都使用了mixin的解决方案,但是这种解决方案局限性很大

      • 首先混入的代码必须是我们已经知晓的,否则我们根本不能从代码中看出混入了什么
      • 其次实际上它的复用性很差,基本上同一个mixin只能使用一次
      • 命名空间重叠,不同mixin会导致命名冲突。
    • 而使用了hooks之后,就很轻松的解决了上述问题

      • 首先,我们用返回值中可以取到需要的变量和函数,这就意味着,hooks做了什么我们无需知道,但是我们可以知道它做到了什么
      • 同一个hooks可以多次使用,返回的变量的命名完全由主流程控制
      • 不同的hooks也不会命名冲突,因为hooks内部会形成一个闭包,内部的变量会被保护起来,是单独的命名空间。
  2. 能够更好的组织代码

    在Vue和React没有使用Hooks之前,React的类组件和Vue的声明式编程都有一个问题,就是不同功能的逻辑被混在一起,因为必须要把数据放一块,方法放一块,生命周期钩子放一块。这就导致一个功能的代码被拆到各个地方,阅读起来很费劲

    但是有了hooks就可以把相关的所有代码写在一起,这样聚合度更高,可读性更好——所以效率更高,bug更少

  3. 比class组件更容易理解

    因为是函数,不需要使用this,就不用考虑this指向的问题,我虽然一直用React的函数组件,但是之前学习的时候,看到那么多this就很头大。

ReactHooks原理

首先每个hooks都有两个阶段——第一次创建的时候的mount阶段,之后的每次都是update阶段。(这两个阶段都是函数)

mount阶段中,会依照我们在函数组件中使用到的hooks按顺序创建一个个hook对象,用不同的hooks函数创建的hook对象之间是不同的,每个hook对象上都储存着自身所需要的数据(hook.memorizedState),不同hook对象有不同的使用数据的逻辑,最后会将这些hook对象按顺序连接起来,组成单向链表,并把第一个挂载在fiber上(fiber.memorizedState)。

至于update阶段,则会执行具体的更新逻辑,根据不同的逻辑创建 effect 对象,并将组件内的 effect 对象串成环状单向链表,放到fiber.updateQueue上面,等到commit阶段进行处理

update阶段中,对比新旧状态是通过hook链表的对应顺序实现的,因此,不能以任何方式改变hooks顺序,否则拿不到匹配的hook就会报错。

Vue3响应式原理

Webpack用过吗

脚手架直接配置好的,但是知道它的原理

首先用初始化参数初始化一个Compiler对象,调用它的run方法开始构建,从入口文件开始,生成AST抽象语法树,然后找到它的依赖,如果它的依赖不是.js文件,那么就要调用loader进行转换,递归重复此步骤直到所有依赖的文件都被转化过,这时候就有了一个依赖关系图谱。再将AST语法树转换成浏览器可识别的code,由于浏览器不支持commenjs语法规范,所以想要处理模块之间的依赖需要重写require函数,用这个函数将所有依赖的文件整合到一个文件中,也就是bundle,然后交给浏览器执行。

什么是闭包

平时是怎么学习的

看什么书

遇到困难时怎么解决的