面试官:开始前允许你先喝口水

363 阅读10分钟

掘友们,大家好!我是阳阳羊。金三银四,最近我也是约到了一个中厂面试,总体感受就是——“口渴”,全程无缝追问,嘴巴没停过,有至少一半没答上来。节奏没把握好,全程都被面试官牵着走,加上女面试官强大的气场,心理上多少还是有点震慑。总之通过这次面试,看清了自己,希望通过写面经的方式,不断总结不足继续加油,如果能给大家带来帮助,那更好不过了!

话不多说,直接看面经:

一. 介绍一下自己的项目

这是一个移动端 app ,项目地址:http://47.96.29.195/ ,技术栈用到了Vue3,Koa,Mysql 等,使用 Vant 搭建界面,实现了移动端适配;引入 VueQuill 富文本编辑器;使用 JWT 保存用户登录状态;后端封装了登录注册以及日记操作相关的接口;使用 Cors 解决了跨域。

二. 移动端适配你是怎么做的

直接引入现成的适配方案

amfe-flexible - npm (npmjs.com)

三. 你还知道其他移动端适配的方法吗

  1. 媒体查询
  2. rem/em
  3. %
  4. vh/vw
  5. 响应式布局

四. rem/em的区别

  • em 单位是相对于父元素的字体大小来计算的。
  • rem 单位是相对于根元素(即html元素)的字体大小来计算的。

五. 移动端如何让用户不能左右滑

  1. 在CSS中添加overflow-x: hidden样式,可以禁止页面水平滚动。
  2. 或者使用JS监听用户滚动事件,如果是水平方向大于垂直方向,则阻止默认事件。

六. 如何区分移动端和PC端

  1. 通过navigator.userAgent获取用户代理信息。
  2. 或者使用媒体查询获取屏幕尺寸,再进行判断。

七. 你了解flex布局吗,有哪些属性?

flex是一种布局方式,可以简便完整响应式地实现页面布局。容器中默认存在两条轴,主轴,交叉轴,默认x轴为主轴,可以用flex-direction来修改主轴的方向

  1. flex-direction : 主轴方向
  2. flex-wrap : 项目是否允许换行
  3. flex-flow : 前两个的简写
  4. justify-content : 项目在主轴上的对齐方式
  5. align-items : 项目在交叉轴上的对齐方式
  6. align-content : 多根轴线(换行)在交叉轴上的对齐方式
  7. flex-grow : 项目在父容器中剩余空间的分配比例
  8. flex-shrink : 项目在父容器不足空间时的缩小比例
  9. flex-basis : 初始尺寸
  10. flex : 前三个的简写

八. grid

Grid布局是 CSS3 引入的二维网格布局,将页面分割成行和列。

  1. grid-template-columns:网格的列数
  2. grid-template-rows: 网格的行数
  3. grid-template-areas: 定义网格的区域
  4. grid-template: 前三个的简写
  5. grid-column-gap:设置网格列间距
  6. grid-row-gap: 设置网格行间距
  7. grid-gap: 前两个的简写
  8. justify-items, align-items: 项目在单元格中的对齐方式
  9. justify-content, align-content: 整个网格在容器中的对齐方式
  10. grid-auto-columns, grid-auto-rows: 未显式指定大小的列和行的大小
  11. grid-auto-flow: 项目在网格容器中的自动放置方式

九. 聊聊绝对定位

  1. 脱离文档流: 绝对定位的元素脱离了文档流,不会影响其他元素的位置。这意味着其他元素不会受到绝对定位元素的影响,也不会对其产生影响。
  2. 相对于父元素定位: 绝对定位元素的位置是相对于最近的已定位(position 属性不为 static)的父元素来计算的。如果没有已定位的父元素,则相对于初始包含块(通常是浏览器窗口)进行定位。
  3. 位置控制: 通过设置 top, right, bottom, left 属性,可以精确地控制元素相对于其定位的参考点的位置。
  4. 重叠控制: 如果多个绝对定位的元素重叠在一起,后面的元素会覆盖前面的元素。可以通过 z-index 属性来控制元素的堆叠顺序。
  5. 尺寸自适应: 绝对定位的元素可以根据内容自动调整尺寸,而不会影响其他元素的布局。

十. JS类型判断

  1. typeof 操作符: 用于检测给定变量的数据类型,返回一个字符串,表示变量的数据类型。常见的类型包括 "number", "string", "boolean", "object", "function", "undefined", 和 "symbol"。但是需要注意的是,typeof null 会返回 "object",这是一个历史遗留问题,不代表 null 确实是对象。

  2. instanceof 操作符: 用于检测构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。操作符只能用于对象,不能用于原始值。

  3. Object.prototype.toString.call() : 可以获取对象的类型。这种方法通常用于检测对象的内部 [[Class]] 属性。

  4. Array.isArray() : 用于检测给定的值是否为数组。

十一. 箭头函数

  1. 语法简洁,只能声明匿名函数
  2. this指向不同,箭头函数的this指向外部函数的this
  3. 不能使用new关键字,没有原型 prototype,不能当成一个构造函数
  4. 没有自己的 arguments,为外层函数的 arguments,如果为全局则没有

十二. ES6新特性

  1. let 和 const 声明:引入了块级作用域的变量声明方式,let 声明的变量具有块级作用域,而 const 声明的变量是常量,值不可变。
  2. 模板字符串 :允许在字符串中插入变量和表达式,使用反引号作为字符串的界定符。
  3. 解构赋值 :允许从数组或对象中提取值并赋给变量,以一种简洁的语法。
  4. Promise :处理异步操作,三种状态一经改变无法修改。
  5. Async/await :相比于Promise,没有自带的错误捕获方法,可以使用 try...catch
  6. Symbol :用于创建唯一的标识符。
  7. Map 和 Set :Map 是一种键值对集合,其中键可以是任意类型;Set 是一种值的集合,其中值也是唯一的。
  8. class 和 extends 关键字:引入了基于原型的类和继承机制,更接近面向对象编程语言。
  9. Module :引入了模块化的新语法,包括 importexport 关键字。

十三. Promise的方法

  1. then() :接收两个回调,第一个是成功的回调,第二个是失败的回调
  2. catch() :接收失败的回调
  3. finally() :不管最终状态如何都会执行的操作
  4. Promise.resolve() :返回一个已经被解析为成功状态的 Promise 对象
  5. Promise.reject() :返回一个已经被解析为失败状态的 Promise 对象。
  6. Promise.all() :接受一个含有多个Promise对象的数组,只有所有对象都成功,则执行最后一个成功的回调
  7. Promise.race() :接受一个含有多个Promise对象的数组,执行第一个状态改变的回调,无论成功还是失败
  8. Promise.any() :接受一个含有多个Promise对象的数组,只要有一个对象成功,则返回第一个成功的回调

十四. 听说过promise值穿透吗

then()或者catch()的参数期望是函数,传入非函数则会发生值穿透。Promise方法链通过return传值,没有return就只是相互独立的任务而已。

十五. Promise 和 Async/await 的区别

  1. Async/await写法更加优雅,不用链式.then()
  2. Promise错误可以通过catch来捕捉,Async/await 没有自带的错误捕获机制,可以用try/catch捕捉

十六. 如何实现图片懒加载

首先我们将需要加载的图片资源地址保存在 img 标签的自定义属性中,获取每一个含有这个自定义属性的img标签,遍历每个img,并获取它的几何属性,监听页面滚动,判断这个图片是否进入了可视区域;若进入可视区域,则创建一个新的Img标签,src属性值为当前遍历到的自定义属性值,当新标签创建完成,删除旧标签,防止进入循环。

十七. 如何维护用户登录状态

面试官:Cookie、Session、Token 有什么区别? - 掘金 (juejin.cn)

十八. 为什么用JWT

JWT是无状态的,服务器不需要保存任何会话数据,减轻了服务器的负担。JWT可以轻松地实现跨域认证和授权。安全性较高,通过签名保证了验证时数据的完整性。

十九. vue3生命周期

  1. beforeCreate:执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
  2. created:组件初始化完毕,各种数据可以使用,常用于异步数据获取
  3. beforeMount:为执行渲染、更新,DOM未创建
  4. mounted:初始化结束,DOM已创建,可用于获取访问数据和DOM元素
  5. beforeUpdate:更新前,可用于获取更新前的各种状态
  6. updated:更新后,所有状态已是最新
  7. beforeUnmount:卸载前,可用于一些定时器或订阅的取消
  8. unmounted:组件实例被卸载之后调用

<KeepAlive>缓存实例的生命周期:

  1. onActivated():首次挂载,以及每次从缓存中被重新插入时调用
  2. onDeactivated():在从 DOM 上移除、进入缓存,以及组件卸载时调用

二十. 父子组件生命周期执行顺序

父亲beforeCreate、父亲created、父亲beforeMount、孩子beforeCreate、孩子created、孩子beforeMount、孩子mounted、父亲mounted、父亲beforeUnmount、孩子beforeUnmount、孩子unmounted、父亲unmounted

二十一. Vue两种路由你知道吗,区别是什么

  • hash模式是通过改变锚点(#)来更新页面URL,并不会触发页面重新加载,我们可以通过window.onhashchange监听到hash的改变,从而处理路由。
  • history模式是通过调用window.history对象上的一系列方法来实现页面的无刷新跳转。

二十二. 组件传值

  1. props
  2. $emit
  3. inject/provide
  4. EventBus
  5. vuex/pinia

二十三. vuex和pinia的区别

Vuex 还是 Pinia ?小孩子才做选择! - 掘金 (juejin.cn)

二十四. 作用域插槽

作用域插槽其实就是带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。

二十五. 自定义指令

相较于v-if这些内置指令,Vue还提供了用户自定义指令的能力,通过 directive 函数来定义,在 setup 函数中作为返回值暴露出来,这样就可以在模板中使用。自定义指令能够为元素添加行为,如懒加载等等,同时可以在指令绑定和解绑时执行一些逻辑,也可以接收参数和修饰符来进行更灵活的控制。

二十六. DFS算法题

// 遍历给定节点id的所有子节点,如有结果以数组形式输出
const tree= [
  {
    id: "1",
    children: [
      {
        id: "2",
        children: [
          {
            id: "3",
            children: [{ id: "4" }]
          },
          { id: "5" },
          {
            id: "6",
            children: [{ id: "7" }]
          },
        ]
      },
      {
        id: "8",
        children: [{ id: "9" }]
      }
    ]
  }
]
// 示例:fn(tree, '1', 'id') // [2,3,4,5,6,7,8,9]
// 示例:fn(tree, '2', 'id') // [3,4,5,6,7]
// 示例:fn(tree, '8', 'id') // [9]
// 示例:fn(tree, '7', 'id') // '当前节点下无子节点'
【答案】(点击展开)
function fn(tree, targetId, type) {
  function dfs(node) {
      if (node.id === targetId) {
          return collectDescendants(node);
      }
      if (node.children) {
          for (let child of node.children) {
              const result = dfs(child);
              if (result) {
                  return result;
              }
          }
      }
      return '当前节点下无子节点';
  }
  // 辅助函数
  function collectDescendants(node) {
      let descendants = [];
      if (node.children) {
          for (let child of node.children) {
              descendants.push(child.id);
              descendants = descendants.concat(collectDescendants(child));
          }
      }
      return descendants;
  }
  return dfs(tree[0]);
}

最后

如果本篇文章对你有帮助,别忘了一键三连,春招一起加油喔~

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!