【2022】8月前端最新面试题(offer 拿来吧你)

2,875 阅读11分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

前言

笔者作为一名23届的大学生,总结了一下自己在8月面试中碰到一些值得记录的题目。希望这篇文章能够为想要面试或正在面试的你提供一些帮助。其中一些问题值得深入研究,我引用了一些优秀文章的链接......

简历

从面试内容来看,大多数面试官都会根据你的简历内容来提问,所以写好简历很重要!

  • 技术名词注意大小写
  • 突出个人亮点,扩充内容。比如在项目中解决 Bug 的过程;比如如何发现的性能问题,如何解决性能问题;比如为何如此选型,目的是什么,较其他有什么优点等等。总体思路就是不写流水账,突出你在项目中具有不错的解决问题的能力和独立思考的能力。
  • 斟酌精通、熟悉等字眼,不要给自己挖坑。
  • 熟悉技术点,确保每一个写上去的技术点自己都能说出点什么,杜绝面试官问你一个技术点,你只能答出会用 API 这种减分的情况。

基础篇

1. css 选择器的优先级

等级定义权重
0级通配选择器、选择符和逻辑组合伪类0
1级标签选择器1
2级类选择器、属性选择器和伪类10
3级id选择器100
4级style属性内联1000
5级!important10000

2. 实现容器的高度是宽度的 1/2

css 实现:

.box {
  padding: 25% 0;
}
或
.box {
  /* 前提宽度是 viewport */
  height: 50vw;
}

js 实现:

const oBox = document.querySelector('.box')
// PC端
window.onload = function() {
  // offsetWidth 会发生重绘
  oBox.style.height = (oBox.offsetWidth / 2) + 'px';
}
// 移动设备
window.onresize = function() {
  oBox.style.height = (oBox.offsetWidth / 2) + 'px';
}

3. 说说 url 的格式

url 主要由四部分组成:协议主机端口路径

url 的详细格式:详解URL的组成

4. 介绍下 css 中的长度单位

以下是一些常见的长度单位:

长度单位含义
px相对于屏幕分辨率
em相对于父元素的字体大小
rem相对于根元素的字体大小
vw相对于视窗的宽度
vh相对于视窗的高度
vm相对于视窗的宽度或高度,取决于哪个更小

5. 说说 ES6 的新特性

  1. let 和 const
  2. class 类
  3. 模块化
  4. Map 和 Set
  5. Promise
  6. 箭头函数
  7. 模板字符串
  8. 延展运算符 ...
  9. 解构赋值
  10. 函数参数的默认值
  11. 对象的属性简写

6. Map 和 Set 的区别

MapSetES6 新增的数据结构,最大的优点是降低了时间复杂度,提高了性能。

区别如下:

  1. Map 是键值对集合,Set 是值的集合。
  2. Map 对象初始化的值为一个二维数组,Set 对象初始化的值为一维数组。
  3. Map对象的键是不能改的,但是值能改,Set 对象只能通过迭代器来更改值。

7. Promise 的方法有哪些?

Promise 的 3 种状态:

  • pending:初始状态,既不是成功,也不是失败状态。
  • fulfilled:意味着操作成功完成。
  • rejected:意味着操作失败。

静态方法

Promise.resolve()

Promise.resolve() 方法返回一个以给定值解析后的 Promise 对象。

Promise.reject()

Promise.reject() 方法返回一个带有拒绝原因的 Promise 对象。

Promise.all()

Promise.all() 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都完成或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败,此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

Promise.race()

Promise.race 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。

实例方法

Promise.prototype.then()

Promise.prototype.catch()

8. 对原型和原型链的理解

关于原型和原型链,大家可以参考下这篇文章: 🍭图解原型和原型链

9. js 中的循环遍历方法有哪些?

数组遍历

  1. 普通 for 循环
  2. forEach
  3. for...of
  • 三者都是从左到右遍历。
  • forEach 无法跳出循环;forfor...of 可以使用 break 或者 continue 跳过或中断。
  • for...of 直接访问的是实际元素。for 遍历数组索引,forEach 回调函数参数更丰富,元素、索引、原数组都可以获取。

对象遍历

  1. for...in
  2. Object.keys
  3. Object.values

关于 js 的循环遍历方法,大家可以参考下这篇文章: JS常用的循环遍历你会几种

10. forEach 和 map 的区别

  • map 方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
  • forEach 方法对数组的每个元素执行一次给定的函数。

11. 防抖和节流

所谓防抖就是在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

应用场景:

  • search 搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  • window 触发 resize 的时候,不断地调整浏览器窗口大小会不断的触发这个事件,用防抖来使其只触发一次。

防抖函数:

function debounce(func, time) {
  let timer;
  return function() {
    clearTimeout(timer);
    let args = arguments;
    timer = setTimeout(() => {
      func.apply(this, args);
    }, time)
  }
}

所谓节流就是规定在一个单位时间内,只能触发一次函数。如果这个单位时间内多次触发函数,只有一次生效。

应用场景:

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)。
  • 监听滚动事件,比如是否滑到底部自动加载更多,用 throttle 来判断。

节流函数:

function throttle(func, time) {
  let t1 = 0;
  return function() {
    let t2 = new Date();
    if (t2 - t1 > time) {
      func.apply(this, arguments);
      t1 = t2;
    }
  }
}

手写篇

1. 手写一个异步事件,参数为时间(毫秒)

const f = () => console.log('aaa')

const sleep = (time) => new Promise((resolve) => {
  setTimeout(resolve, time)
})

sleep(2000).then(f)  // 2s后打印 aaa

2. 手写 Promise.all

在进行手写之前,我们需分步进行分析:

  1. 接收一个 Promise 实例的数组作为参数
  2. 这个方法返回一个新的 promise 对象
  3. 遍历传入的参数,如果参数不是 promise,Promise.resolve 在外面包一层,使其一定是一个 promise 对象
  4. 所有 promise resolve 成功,才算成功,返回值与参数数组顺序一致,用数组的下标来设置每个 resolve 值
  5. 只要有一个失败,进行 catch
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {  // 类型校验
      throw new Error('must be array')
    }
    var resolveCounter = 0;
    var promiseNum = promises.length;
    var resolveResult = [];
    for (let i = 0; i < promiseNum; i++) {
      // 启动并发
      Promise.resolve(promises[i])
        .then(value => {
          resolveCounter++;
          resolveResult[i] = value;
          if (resolveCounter == promiseNum) {
            return resolve(resolveResult);
          }
        }, err => {
          return reject(err)
        })
    }
  })
}
// 测试用例
let p1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(1)
  }, 1000)
})

let p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(2)
  }, 2000)
})

let p3 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(3)
  }, 3000)
})

promiseAll([p1, p2, p3])
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log(err)
  })

3. 手写一个进度条组件

关于如何手写一个 react 进度条组件,大家可以参考一下这篇文章: Typescript 入门写一个 react 进度条组件

React 篇

1. 为什么选择使用框架?

框架带来的好处:

  1. 组件化:其中 React 的组件化最为彻底,甚至可以到函数级别的原子组件,高度组件化可以让我们的工程易于维护、易于组合拓展。
  2. 天然分层:JQuery 时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是 MVC 还是 MVVM 模式都能帮助我们进行分层,代码解耦更易于读写。
  3. 开发效率: 现代前端框架都默认自动更新 DOM,减少了开发成本,提高了开发效率,从根本上解决了 UI 和状态同步问题。
  4. 生态: 现代主流前端框架都自带生态,不管是数据流管理还是 UI 库都有成熟的解决方案。

2. React 事件机制

关于 React 事件机制,大家可以参考下面这篇文章: React 事件机制

3. React 合成事件和原生 DOM 事件哪个先执行?

react 事件和原生事件的区别是:react 中的事件是绑定到 document 上面;而原生的事件是绑定到 dom 上面。相对绑定的地方来说,dom 上的事件要优先于 document 上的事件执行,react 的事件对象是合成对象,不是原生的。

参考文档:react事件和原生事件有什么区别

4. React 高阶组件

React 高阶组件(HOC),它是灵活使用react组件的一种技巧,高阶组件本身不是组件,它是一个参数为组件,返回值也为组件的函数。高阶组件用于强化组件,复用逻辑,提升渲染性能等

官方解释:

高阶组件(HOC) 是 React 中用于复用组件逻辑的一种高级技巧,HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

关于 React 高阶组件的详细介绍,大家可以参考这篇文章:「react进阶」一文吃透React高阶组件(HOC)

5. 你知道哪些 React Hooks?

React hooks 是 React 16.8 以后新增的钩子 API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态 state 的缺陷。

这里我把一些常见的 hooks 列举出来:

hooks 功能分类hooks具体功能
数据更新驱动useState数据驱动更新
数据更新驱动useReducer订阅状态,创建 reducer,更新视图
执行副作用useEffect异步状态下,视图更新后执行
执行副作用useLayoutEffect同步状态下,视图更新前执行
执行副作用useInsertionEffect(v18新增)用于处理 css in js 缺陷问题
状态获取与传递useContext订阅并获取 react context 上下文,用于跨层级状态传递
状态获取与传递useRef获取元素或组件实例
状态派生与保存useMemo派生并缓存新的状态,常做性能优化
状态派生与保存useCallback缓存状态,常用于缓存提供给子代组件的 callback 回调函数

6. useMemo 和 useCallback 的区别

返回值不同

useMemo 返回的是函数执行的结果,useCallback 返回的是函数的引用

优化对象不同

useMemo 针对于优化当前组件高开销的计算,useCallback 针对于优化子组件的渲染

7. useEffect 的第二个参数

  • 当没有第二个参数时,组件的挂载和更新都会执行。
  • 当第二个参数为空数组时,组件挂载调用一次之后不再执行。
  • 当第二个参数只有一个值时,该值有变化就执行。
  • 当第二个参数是有多个值的数组时,会比较每一个值,有一个不相等就执行。

8. React 组件之间如何进行通信?

  • 父组件向子组件通信:父组件通过向子组件传 props 的方式,向子组件进行通信。
  • 子组件向父组件通信:props + 回调的方式,父组件向子组件传递 props 进行通信,此 props 为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中。
  • 跨层级通信:Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过 Context 通信再适合不过。
  • 全局状态管理工具:借助 Redux 或者 Mobx 等全局状态管理工具进行通信,这种工具会维护一个全局状态中心 store,并根据不同的事件产生新的状态。

其他

  1. 为什么选择学前端?
  2. 平时是怎么学习的?
  3. 说说自己的个人优势
  4. 谈谈未来的发展

以上问题欢迎大家在评论区留言~

最后

以上就是笔者在面试了几家公司后总结的部分面试题,如有不足欢迎大家指出,在这个金九银十的时间段,希望你我都能收获自己满意的offer,你的鼓励就是我最大的动力!🌹🌹🌹