Myles
4106 字
21 分钟
面试复盘
2023-10-12

23 年 10 月 11 日#

国庆来了第一次面试,这家公司我是非常满意的,公司氛围很好,hr 和面试官给我的感觉都很和善。可惜吧,自己没抓住机会,到现在还有点怅然若失。面试前太懈怠了,有些概念没有强迫自己去记住,对自己简历上的项目理解的也浮于表面。

面试官的提问方式#

​ 一开始我以为面试官会先问些八股文,然后再问项目内容。结果面试官是从项目出发提出问题然后根据我的回答引申出的概念再来提问,如此往复进行的面试过程。

暴露出来的问题#

使用 typescript 对 axios 二次封装(全局错误拦截、常用请求封装、全局 loading、取消重复请求)。因为这放在我项目的第一个工作,面试官首先就问了我是怎么封装。#

主要是对请求拦截器与响应拦截器的二次封装。在请求拦截器里添加全局 loading 和请求头添加身份验证 token。在响应拦截器通过服务器返回的状态码进行全局错误拦截,错误消息的提示。状态码等于 401 登录失效处理(本地清除 token,路由跳转登陆界面)。 取消重复请求,具体实现上,通过将每个请求的标识(URL)以及对应的取消函数存储在一个 Map 中,来确保每个请求只被执行一次。每次发起请求时,会先检查该请求是否已经在 map 集合里存在,如果存在取消当前请求。

首屏优化加载#

面试官 : 首屏加载速度慢怎么优化?掘金好文!前端性能优化

状态码#

HTTP 响应状态码mozilla 文档总结的很全

对表格的所有操作基本都封装成了 Hooks (表格数据搜索、重置、查询、分页、多选、单条数据操作、文件上传、下载、格式化单元格内容)。这也是我项目上写的工作,面试官问了怎么进行多个文件上传的#

首先定义 reactive 响应式对象,包括表格数据、查询参数、分页数据等。然后定义获取表格数据方法(接受搜索参数),拿到表格数据和更新分页信息。搜索就是更新搜索参数,重新获取数据。重置将搜索、分页设为默认值,重新获取数据。文件上传则是新建 formData 实例加入 append file 然后向后端发送 post 请求。文件下载则是后端返回数据前端读取为 blob 格式,然后创建 blob URL,创建 a 标签 href 属性为 url,download 属性为文件名,点击下载,完成移除 a 标签。

封装使用了哪些思想(工厂模式等)?#

封装采用了工厂模式设计思想,自定义 hooks 函数 useTable 充当工厂函数,通过传入相应的参数,创建一个具有特定功能的表格操作实例。这个实例封装了获取表格数据、查询、重置、分页等操作的方法,并提供了响应式的状态对象供使用者访问和监听。

响应式布局和自适应布局区别?Bootstrap 是怎么实现响应式的?#

实现方式:响应式布局:使用 CSS 媒体查询来根据设备的屏幕大小和特性,调整网页的样式和布局。通过设置不同的 CSS 规则集,可以在不同的屏幕尺寸下呈现不同的布局效果;自适应布局:通过使用百分比、弹性盒子(flexbox)等 CSS 技术,使网页元素相对于父容器或视口自动调整大小和位置,以适应不同的屏幕尺寸。适应性程度:响应式布局:能够适应多个屏幕尺寸,并提供更灵活的响应能力。可以根据设备的宽度、高度、方向等特性进行适配,以提供最佳的用户体验。通过媒体查询,可以针对不同的屏幕尺寸应用不同的样式和布局。自适应布局:主要通过百分比和弹性盒子等技术实现元素的自适应调整。相对于响应式布局而言,自适应布局的适应范围较为有限,更多地依赖于容器或视口的尺寸来进行自适应。综上所述,响应式布局和自适应布局虽然都能够实现页面在不同设备上的适应性,但响应式布局在适应性范围和灵活性方面更为强大,而自适应布局相对简单,更多地依赖于容器的尺寸进行适应调整。Bootstrap 是一个流行的前端框架,它使用响应式设计来适应各种设备的屏幕尺寸。Bootstrap 的响应式设计基于以下几个方面:栅格系统(Grid System):Bootstrap 将页面划分为 12 等份,每个等份称为一列(column),列之间使用栅格(grid)进行分隔。通过设置不同的列数和偏移量,可以创建出多种响应式布局。媒体查询(Media Query):Bootstrap 使用 CSS 媒体查询来根据不同的屏幕尺寸和设备类型应用不同的 CSS 样式。例如,可以使用@media 规则来为不同的屏幕大小设置不同的字体大小、行距等属性。响应式 CSS 类:Bootstrap 提供了多种响应式 CSS 类,以方便开发者对网页元素进行针对性的响应式调整,例如可更改元素的显示与隐藏、可更改元素的宽度等。通过以上的设计,Bootstrap 能够自动适应各种屏幕尺寸,从而提供给用户更好的使用体验。

Scss 等预处理器等优势在哪?举出 scss 中定义方法解决浏览器兼容的例子?#

变量和嵌套,混合(Mixins)和继承,函数和运算,导入和模块化

// 定义兼容性前缀的Mixin
@mixin prefix($property, $value) {
  -webkit-#{$property}: $value;
  -moz-#{$property}: $value;
  -ms-#{$property}: $value;
  -o-#{$property}: $value;
  #{$property}: $value;
}

// 使用兼容性前缀的Mixin
.box {
  @include prefix(border-radius, 5px);
}

使用 vue-router 进行路由权限拦截(403 页面)、页面按钮权限配置、路由懒加载。项目写的工作,面试官问了页面按钮权限配置#

路由权限拦截:添加一个全局前置守卫(beforeEach)来拦截需要进行权限控制的路由。在每次路由切换之前进行权限检查,判断用户是否具有访问该路由的权限。如果有权限,则继续跳转到下一个路由,否则跳转到 403 页面。页面按钮权限:获取与当前路由名称对应的按钮权限列表,其中键为按钮名称,值为true。然后通过 v-if 决定显示与隐藏。或者自定义一个 v-auth 指令,获取当前页面的权限码列表,判断绑定的状态码是否在列表内,没有则移除元素。懒加载:首先,在路由配置文件中将要懒加载的路由组件改为使用 import() 异步加载方式。在 Webpack 4 及以上版本中,默认支持路由懒加载。在打包后生成的 js 文件中,Vue Router 会将每个路由组件打包成单独的 js 文件,这样可以在用户需要访问该组件时再进行加载,从而减少初始加载的时间和带宽占用。

防抖与节流,在业务中的案例#

防抖是指在短时间内多次触发同一个事件时,只执行一次函数。具体实现方式是,在事件触发后设立一个定时器,若在定时器的时间范围内再次触发事件,则重新开始计时,直到定时器结束后执行函数。防抖常用于输入框搜索、窗口大小调整等场景,可以减少事件频繁触发导致的性能问题。节流是指在一定时间间隔内只执行一次函数。具体实现方式是,在事件触发后先执行函数,然后设立一个定时器,在定时器的时间范围内再次触发事件时,不执行函数,直到定时器结束后再次执行函数。节流常用于滚动加载、鼠标移动等场景,可以限制函数的执行次数,减少不必要的计算和渲染。在项目中,防抖和节流可以用于以下场景:

  1. 表单输入实时校验:使用防抖来延迟校验操作,减少频繁的网络请求。
  2. 页面滚动事件处理:使用节流来优化滚动事件的处理,减少过多的重绘和计算。
  3. 避免按钮重复点击:使用防抖来控制按钮的点击事件,避免用户重复操作。
  4. 动画触发控制:使用节流来限制动画的触发频率,保证流畅度并节省资源。

promise 是怎么解决回调地狱的?#

回调地狱是指多个异步操作嵌套时,由于异步操作执行时间不确定性,导致回调函数层层嵌套,从而导致代码可读性和可维护性变差的问题。Promise 诞生的初衷就是为了解决回调地狱的问题。

Promise 通过将异步操作的状态封装成对象的方式,让异步操作可以像同步操作一样顺序执行,可以通过链式调用的方式实现代码流程的整洁和简洁。同时,Promise 对象提供了then()catch()finally()等方法,使我们可以更好地处理异步操作成功、失败、完成等状态,大大提高了异步操作的可读性和可维护性。

当异步操作的完成状态发生改变时,即从未完成变为成功或失败,Promise 都会在内部管理的队列中寻找指定该 Promise 对象的回调函数,将其添加到微任务队列中,最后在 JavaScript 主线程空闲的时候执行这些回调函数。

这样,我们就可以使用 Promise 来优雅地处理异步操作,避免回调地狱的问题。掘金好文

Vue 生命周期,分别做了哪些事情,对应什么业务场景#

一文带你弄懂 Vue 八大生命周期钩子函数

typescript 怎么实现函数重载?#

TypeScript 泛型与重载

Webpack 的构建流程?#

2023 前端面试系列— webpack & Git 篇

webpack 与 vite 的区别?#

深入理解 Vite 核心原理

项目遇到的困难,怎么解决的?#

分享一下我近期的经验,之前项目也碰到过用起来很卡的情况,就是用 element ui 的 tab 切换组件时,点击 tab 切换非常卡,非常耗时,在排除了网络请求和 js 代码执行时间过长等原因后,跑了一次 perfermance,结果发现大部分时间都花费在了 DOM GC 上了,分析了下原因可能时 dom 结构太多导致每次 tab 切换渲染太耗时了。由于我每个 tab 里面的 html 结构都一样,都是一个 table,只是每次 tab 切换时请求的数据不一样,我就把 table 抽离出来了,放到 tab 组件外面,然后 tab 里面就空了,就没有那么多 dom 了,tab 切换就不卡了,很流畅。性能优化

23 年 11 月 13 日#

这期间也面了几家公司,因为都问的比较零碎,所以放到现在统一总结下。大公司还是更看重 JS 基础和代码规范,小公司看重的是项目能力。

面试问题#

讲一下 typescript 中 utility types,项目中那些应用#

typescript 提供一些工具,来辅助进行常用的类型转换。工具类型

typescript 中的枚举,枚举没赋值默认是什么#

枚举是一种用户定义的数据类型,可以使用它们来定义一组命名的常量。 使用枚举可以清晰地表达意图。

每个枚举都带有一个值,他是可以是常量也可以计算得到,枚举第一个成员没有初始化,默认是 0,依次递增。

typescript 中 type 和 interface 的区别#

两者都可以描述对象或者函数。不同点在于 interface 可以对已经存在的接口添加新的字段,type 创建后不能被改变。type 可以声明基本类型别名,联合类型,元组等类型,type 还可以使用 typeof 获取实例的类型进行赋值

项目中的亮点#

谈一下 el-table 二次封装。待填坑。

文件的分片上传与下载。

上传:大概思路是使用 file api 的 silce()方法和 size 属性进行循环分块。然后就是文件的唯一值了,如何告诉后端上传的两个文件是同一个文件,显然使用文件名作为唯一标识符不合适。这个时候我想到用 MD5(spark-md5)对文件加密获取唯一的 hash 值,因为计算 hash 值非常消耗计算机的 cpu 所以就使用了 web-worker,在 work 线程中,接收文件切片利用 FileReader 读取为 ArrayBuffer 并不断传入 spark-md5 中,使用 postMessage 与主线程进行通讯,前端这边再用 Promise.all()并发切片,然后发送请求通知后端合并切片。

下载:JavaScript 中如何实现大文件并行下载?

大文件上传处理#

你不知道的 blob

ref 和 shallowRef 区别#

shallowRef 是 ref 的浅层作用形式。和 ref 不同,shallowRef 的内部值不会被深层递归地转为响应式。只有对.value 的访问时响应式的

vue 生命周期以及组件卸载时做那些事情#

Vue 的生命周期包括以下阶段:

  1. 创建阶段(Creation):
    • beforeCreate:在实例初始化之后,数据观测之前调用。此时无法访问到数据和 DOM。
    • created:在实例创建完成后被调用。可以访问到数据和进行简单的 DOM 操作。
  2. 模板编译与挂载阶段(Compilation and Mounting):
    • beforeMount:在模板编译/挂载之前调用。此时模板已经编译完成,但尚未挂载到 DOM 上。
    • mounted:在模板编译/挂载完成之后调用。此时模板已经挂载到 DOM 上,可以进行 DOM 操作。
  3. 更新阶段(Updating):
    • beforeUpdate:在数据更新之前调用。此时虚拟 DOM 已经重新渲染,但尚未应用到实际的 DOM 上。
    • updated:在数据更新之后调用。此时虚拟 DOM 已经重新渲染,并且更新已经应用到实际的 DOM 上。
  4. 销毁阶段(Destruction):
    • beforeDestroy:在实例销毁之前调用。此时实例仍然完全可用。
    • destroyed:在实例销毁之后调用。此时实例及其所有的指令已被解绑,事件监听器已被移除,可以进行清理操作。

在这些生命周期阶段中,你可以执行以下操作:

  • 在 created 阶段可以进行初始化数据、异步请求数据等操作。

  • 在 mounted 阶段可以进行 DOM 操作、绑定事件监听器等操作。

  • 在 updated 阶段可以根据数据的变化,执行一些需要基于更新后的 DOM 进行的操作。

  • 在 beforeDestroy 阶段可以进行清理工作,如解绑事件监听器、清除定时器、取消订阅等。

  • 在 destroyed 阶段可以进行最终的清理工作,如释放资源、取消异步任务等。

  • 命名规范,git 提交规范#

前端代码规范

git commit 规范

setTimeout 和 setInterval 区别#

定时器是指定时间间隔将代码加入消息队列,而不是执行代码。如果前面有很多任务,或者某个任务等待时间较长,定时器预期执行时间就会与我们设想的不一致。而 setInterval 在每次把任务 push 到消息队列前,都要进行判断(上一个任务是否仍在队列中,如果有则不添加,没有则添加)。一般使用 setTimeout 模拟 setInterval 来规避缺点

面试复盘
https://blog.panda74.fun/posts/note/interview-reply/reply/
作者
Panda74
发布于
2023-10-12
许可协议
CC BY-NC-SA 4.0