前端面试常见问题深度解析

在前端开发的面试中,常常会涉及到数据结构、算法、框架使用、性能优化等多方面的知识。

下面将对一系列我最近在前端面试中常见问题进行详细解答,希望能为正在准备面试或者想要深入学习前端技术的开发者提供帮助。

一、数据结构相关问题

1. 常见的数据结构

常见的数据结构包括数组、链表、栈、队列、树(如二叉树)、图、哈希表等。

  • 数组:具有随机访问元素的特性,通过下标可以快速定位到相应元素。例如在 JavaScript 中,let arr = [1, 2, 3]; 可以通过 arr[0] 快速获取第一个元素。
  • 链表:由节点组成,每个节点包含数据和指向下一个节点的指针。它的优点是插入和删除操作效率高,缺点是随机访问效率低。
  • :遵循后进先出(LIFO)原则,就像一摞盘子,最后放上去的盘子最先被拿走。常见的应用场景有函数调用栈。
  • 队列:遵循先进先出(FIFO)原则,类似于排队,先到的人先接受服务。例如消息队列。
  • :是一种分层结构,二叉树是树的一种特殊形式,每个节点最多有两个子节点。
  • :由节点和边构成,用于表示对象之间的关系,如社交网络中的用户关系。
  • 哈希表:通过哈希函数将键映射到存储位置,实现快速的数据存储和查找。

2. 二叉树的特点

  • 节点数量限制:每个节点最多有两个子节点,分别称为左子节点和右子节点。
  • 层次结构:具有明显的层次结构,包含根节点、内部节点和叶子节点。根节点是树的起始点,叶子节点是没有子节点的节点。
  • 子树有序:左右子树有明确的区分,不能随意交换位置。
  • 遍历方式多样:常见的遍历方式有前序遍历(根 - 左 - 右)、中序遍历(左 - 根 - 右)、后序遍历(左 - 右 - 根)和层序遍历。

3. 链表的特点及进出顺序

链表本身不具备固定的先进先出或先进后出特性。它是一种线性数据结构,节点通过指针相连,方便进行插入和删除操作。若要实现先进先出,可以使用链表构建队列;若要实现先进后出,可以使用链表构建栈。

4. 链表插入和删除的复杂度

链表插入和删除操作的时间复杂度通常为 O (1),前提是已经知道要操作的节点位置。但如果需要先找到该位置,时间复杂度则为 O (n),其中 n 是链表的长度。对于插入 m - n + 1 个节点,如果已知插入位置,时间复杂度为 O (m - n + 1);如果需要先查找插入位置,时间复杂度为 O (n * (m - n + 1))。

5. 数据结构中矩阵的存储方式

矩阵存储方式有行主序和列主序两种。行主序是按行依次存储矩阵元素,列主序是按列依次存储矩阵元素。不同的编程语言和应用场景可能会采用不同的存储方式。例如,C 语言默认采用行主序存储矩阵。

二、算法相关问题

1. 排序算法种类及选择排序种类

常见的排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序、希尔排序等。
选择排序常见的有简单选择排序和堆排序。

  • 简单选择排序:每次从未排序序列中选出最小(大)元素,与未排序序列的第一个元素交换位置,直到整个序列有序。
  • 堆排序:利用堆这种数据结构来进行排序。堆是一种完全二叉树,分为大顶堆和小顶堆。大顶堆的每个节点的值都大于或等于其子节点的值,小顶堆则相反。

2. 广度优先和深度优先算法的实际应用

  • 广度优先搜索(BFS)
    • 最短路径问题:在无权图中寻找两个节点之间的最短路径。例如在地图导航中,寻找两个地点之间的最少步数路径。
    • 社交网络中的好友推荐:查找某用户的一度、二度好友等。
  • 深度优先搜索(DFS)
    • 迷宫求解:探索迷宫的所有可能路径,找到从起点到终点的路径。
    • 图的连通性检测:判断图中是否存在从一个节点到另一个节点的路径。

三、数据库相关问题

数据库设计学习内容及版本

数据库设计主要学习需求分析、概念结构设计、逻辑结构设计、物理结构设计、数据库实施和数据库运行维护等内容。不同的应用场景会选用不同的数据库管理系统,常见的版本如 MySQL 8.0、Oracle 19c、SQL Server 2019 等。

四、Vue 相关问题

1. 大学学习的 Vue 版本

不同学校的课程设置不同,可能会学习 Vue2 或 Vue3。Vue2 常见的版本有 Vue 2.6.x 等。

2. Vue2 和 Vue3 的区别及重大改变

  • 响应式系统:Vue2 使用 Object.defineProperty() 来实现响应式,而 Vue3 使用 Proxy 对象。Object.defineProperty() 存在一些局限性,如无法检测对象属性的添加和删除,而 Proxy 可以监听对象属性的变化,实现更强大的响应式功能。
  • 组合式 API:Vue3 引入组合式 API,使代码复用和逻辑组织更方便。通过组合式 API,可以将相关的逻辑封装在一起,提高代码的可读性和可维护性。
  • 性能优化:Vue3 在编译和渲染方面有性能提升。例如,Vue3 采用了静态提升和 PatchFlag 等技术,减少了不必要的渲染。

3. Proxy 的作用及应用场景

Proxy 是 ES6 新增的一个对象,用于创建一个对象的代理,从而实现对该对象的基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。除了在 Vue3 中用于实现响应式系统外,还有以下应用场景:

  • 数据验证:在对象属性赋值时进行验证,确保数据的合法性。
  • 日志记录:记录对象属性的访问和修改操作,方便调试和监控。
  • 访问控制:限制对对象某些属性的访问,提高数据的安全性。

4. Vue2 监听不到的情况

  • 对象新增属性:Vue2 无法检测到对象属性的添加。例如,给一个响应式对象新增一个属性,Vue2 不会自动更新视图。可以使用 Vue.set() 或 this.$set() 方法来解决这个问题。
  • 数组通过索引修改元素或修改数组长度:Vue2 无法检测到通过索引直接修改数组元素或修改数组长度的变化。可以使用数组的变异方法(如 push()pop()splice() 等)来触发视图更新。

5. 对 Vue 渐进式框架的理解

Vue 是渐进式框架意味着可以根据项目需求逐步引入 Vue 的功能。可以从一个简单的页面开始,只使用 Vue 的部分功能,随着项目的发展,再逐步引入路由、状态管理等更多功能。这种特性使得 Vue 具有很强的灵活性和可扩展性,适合不同规模的项目。

6. Vue 中的 DOM 变更、删除策略及虚拟 DOM

  • DOM 变更:包括元素的插入、删除、修改属性、修改文本内容等。Vue 通过虚拟 DOM 来管理 DOM 变更,提高了渲染效率。
  • 删除策略:Vue 会根据虚拟 DOM 的比对结果,决定是否删除真实 DOM 元素。当虚拟 DOM 中某个节点被移除时,对应的真实 DOM 元素也会被删除。
  • 虚拟 DOM 形式:虚拟 DOM 是一种轻量级的 JavaScript 对象,它是真实 DOM 的抽象表示。Vue 在更新视图时,会先比较新旧虚拟 DOM 的差异,然后只更新需要更新的真实 DOM 部分。当新旧虚拟 DOM 中某个节点的差异达到需要删除的条件时(如节点被移除),就会删除对应的真实 DOM 元素。比对过程通常采用 Diff 算法,通过比较新旧虚拟 DOM 的节点属性和子节点,找出差异并进行更新。

7. 父子组件及祖孙组件通信方式

  • 父子组件通信方式
    • props:父组件向子组件传递数据。子组件通过定义 props 选项来接收父组件传递的数据。
    • 自定义事件:子组件向父组件发送消息。子组件通过 $emit() 方法触发自定义事件,父组件通过监听该事件来接收消息。
  • 祖孙组件通信方式
    • 事件总线(Event Bus):创建一个全局的事件对象,用于组件之间的通信。祖孙组件都可以通过该事件对象发送和接收消息。
    • provide/inject:Vue 提供的一种依赖注入方式,允许祖先组件向后代组件传递数据。祖先组件通过 provide 选项提供数据,后代组件通过 inject 选项注入数据。
    • Vuex 或 Pinia:状态管理库,用于组件之间的状态共享。祖孙组件都可以访问和修改状态管理库中的数据。

8. Pinia 和 Vuex 的了解

Pinia 和 Vuex 都是 Vue.js 的状态管理库。Pinia 是 Vue 3 推荐的状态管理库,它的 API 更简洁,支持组合式 API,并且具有更好的类型推导。Vuex 是 Vue 2 常用的状态管理库,它采用单向数据流的设计思想,通过 mutations、actions 和 getters 来管理状态。

五、前端其他相关问题

1. Node.js 学习内容

Node.js 学习内容包括:

  • 模块系统:如 CommonJS 模块规范,了解如何创建和使用模块。通过 require() 方法引入模块,使用 module.exports 或 exports 导出模块。
  • 事件驱动:学习如何使用事件模块来处理异步事件。通过 EventEmitter 类创建事件发射器,监听和触发事件。
  • 文件系统操作:如读取、写入文件等。使用 fs 模块进行文件操作,包括 fs.readFile()fs.writeFile() 等方法。
  • 网络编程:如创建 HTTP 服务器和客户端。使用 http 模块创建 HTTP 服务器和客户端,处理 HTTP 请求和响应。
  • 异步编程:包括回调函数、Promise、async/await 等。异步编程可以提高程序的性能和响应能力,避免阻塞主线程。
  • Express 或 Koa 等 Web 框架:用于构建 Web 应用。Express 是一个简洁而灵活的 Node.js Web 应用框架,Koa 是一个基于 Node.js 的下一代 Web 框架,它更加轻量级和高效。

2. 手写兼容主流浏览器的问题及解决方案

  • 兼容问题和差异
    • CSS 前缀:不同浏览器对 CSS3 属性可能需要不同的前缀,如 -webkit--moz- 等。可以使用 Autoprefixer 工具自动添加浏览器前缀。
    • JavaScript 特性支持:不同浏览器对 ES6+ 等新特性的支持程度不同。可以使用 Babel 工具将 ES6+ 代码转换为兼容旧浏览器的代码。
    • 盒模型:IE 浏览器在怪异模式下盒模型与标准模式不同。可以通过设置  声明来使用标准模式。
  • 浏览器内核:常见的有 Trident(IE 浏览器)、Gecko(Firefox 浏览器)、Blink(Chrome、Opera 等浏览器)、WebKit(Safari 浏览器)。
  • Web 端和移动端适配方式
    • 媒体查询:根据不同的屏幕尺寸应用不同的 CSS 样式。通过 @media 规则在 CSS 中定义不同的媒体查询条件。
    • rem/em 布局:使用相对单位进行布局。rem 是相对于根元素的字体大小,em 是相对于父元素的字体大小。
    • viewport 布局:通过设置  标签来控制页面在移动端的显示。例如, 可以使页面宽度自适应设备宽度。
  • Bootstrap 实现移动端兼容:Bootstrap 采用响应式网格系统,通过不同的类名(如 col-sm-col-md- 等)来实现不同屏幕尺寸下的布局。根据屏幕宽度的不同,自动调整列的宽度和排列方式。
  • 其他大屏小屏兼容方式
    • 弹性布局(Flexbox)和网格布局(Grid):能自适应不同屏幕尺寸。Flexbox 适用于一维布局,Grid 适用于二维布局。
    • 响应式图片:使用  标签或 srcset 属性根据屏幕尺寸加载不同分辨率的图片。

3. ES6 相对 ES5 的新增内容

  • 块级作用域let 和 const 关键字。let 声明的变量具有块级作用域,const 声明的常量一旦赋值就不能再修改。
  • 箭头函数:简化函数定义。箭头函数没有自己的 thisargumentssuper 或 new.target,它的 this 值继承自外层函数。
  • 模板字符串:方便字符串拼接。使用反引号(`)包裹字符串,可以在字符串中嵌入变量和表达式。
  • 解构赋值:方便对象和数组的赋值。可以通过解构赋值从对象或数组中提取值并赋值给变量。
  • Promise 对象:处理异步操作。Promise 有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),可以通过 then() 和 catch() 方法处理异步操作的结果。
  • async/await:更优雅地处理异步操作。async 函数返回一个 Promise 对象,await 关键字只能在 async 函数中使用,用于等待一个 Promise 对象的解决。
  • 类和继承:引入 class 关键字和 extends 关键字。class 关键字用于定义类,extends 关键字用于实现类的继承。
  • 模块化import 和 export 关键字。export 用于导出模块中的变量、函数或类,import 用于导入其他模块中的内容。

4. 组件化开发的概念

组件化开发是将页面拆分成多个独立的、可复用的组件,每个组件有自己的 HTML、CSS 和 JavaScript 代码。组件化开发提高了代码的可维护性和可复用性,使得开发人员可以更加高效地开发和维护大型项目。

5. Webpack 基本构建流程及配置

  • 基本构建流程
    1. 解析入口文件:Webpack 从入口文件开始,递归地解析所有依赖的模块。
    2. 模块打包:将所有模块打包成一个或多个文件。
    3. 资源处理:使用 loader 对不同类型的资源(如 CSS、图片等)进行处理。
    4. 输出文件:将打包后的文件输出到指定的目录。
  • 配置方法:创建一个 webpack.config.js 文件,在其中配置入口文件、输出文件、loader、plugin 等信息。例如:
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: []
};

6. Git 常用操作命令及上传命令

  • 常用操作命令
    • git clone:克隆远程仓库到本地。例如,git clone https://github.com/username/repo.git 可以将指定的远程仓库克隆到本地。
    • git add:将文件添加到暂存区。例如,git add index.html 可以将 index.html 文件添加到暂存区。
    • git commit:将暂存区的文件提交到本地仓库。例如,git commit -m "Add index.html" 可以将暂存区的文件提交到本地仓库,并添加提交信息。
    • git push:将本地仓库的文件推送到远程仓库。例如,git push origin main 可以将本地仓库的 main 分支推送到远程仓库的 main 分支。
    • git pull:从远程仓库拉取文件并合并到本地仓库。例如,git pull origin main 可以从远程仓库的 main 分支拉取文件并合并到本地仓库的 main 分支。
    • git branch:查看、创建和切换分支。例如,git branch 可以查看本地分支,git branch new-branch 可以创建一个新的分支,git checkout new-branch 可以切换到指定的分支。
    • git merge:合并分支。例如,git merge new-branch 可以将 new-branch 分支合并到当前分支。
  • 上传命令git push <远程仓库名> <分支名>,例如 git push origin main

7. WebSocket 通信相关问题

  • 特点
    • 双向通信:客户端和服务器可以随时向对方发送数据。
    • 实时性高:数据传输几乎没有延迟。
    • 较少的开销:与 HTTP 相比,WebSocket 连接建立后,只需要较少的头部信息。
  • 可视化平台应用:一些实时监控系统、金融交易平台、在线游戏等可能会使用 WebSocket 进行实时通信。
  • 实时预警:可以是系统监控中的异常报警、金融市场的价格波动预警等。
  • 用户状态判断:可以在客户端连接 WebSocket 时,发送用户标识信息(如用户 ID)到服务器,服务器根据这些信息判断用户状态。判断可以在推送消息前进行,也可以在连接建立时进行。是否一进入系统就连接 WebSocket 取决于具体的业务需求。
  • 重连机制:可以通过在客户端监听 WebSocket 的 close 事件,当连接断开时,触发重连逻辑。重连机制可以设置重试次数和重试间隔时间。例如,可以设置最多重试 5 次,每次重试间隔时间为 5 秒。也可以根据实际情况选择无限重连,但需要注意避免过度消耗资源。

8. 前端执行异步函数的操作

  • 网络请求:如使用 fetch 或 axios 发送 HTTP 请求。这些请求是异步的,不会阻塞主线程,可以通过 then() 或 async/await 处理响应结果。
  • 定时器:如 setTimeout 和 setIntervalsetTimeout 用于在指定的时间后执行一次回调函数,setInterval 用于每隔指定的时间执行一次回调函数。
  • 文件读取:在浏览器中使用 FileReader 读取文件内容。文件读取是异步操作,可以通过监听 FileReader 的事件来处理读取结果。
  • 动画效果:使用 requestAnimationFrame 实现动画。requestAnimationFrame 会在下一次重绘之前执行回调函数,实现平滑的动画效果。

9. 前端性能优化效果查看

可以使用浏览器的开发者工具来查看性能优化效果。例如,在 Chrome 浏览器中:

  1. 打开开发者工具(按 F12 或右键选择 “检查”)。
  2. 切换到 “Performance” 面板。
  3. 点击录制按钮,然后进行页面操作。
  4. 停止录制后,查看 “Timings” 部分,找到 “Load” 或 “DOMContentLoaded” 时间,对比优化前后的时间。

10. TypeScript 中的泛型和重载

  • 泛型:泛型是一种在定义函数、类或接口时不预先指定具体类型,而是在使用时再指定类型的机制。泛型可以提高代码的复用性和类型安全性。例如:
function identity(arg: T): T {
  return arg;
}

let output1 = identity("myString");
let output2 = identity(100);
  • 重载:重载允许一个函数接受不同类型和数量的参数,并根据参数的不同执行不同的逻辑。在 TypeScript 中,需要先定义多个函数签名,然后实现一个通用的函数。例如:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

let result1 = add(1, 2);
let result2 = add("Hello", " World");

你可能感兴趣的:(前端,前端,面试,职场和发展,开发语言,数据结构,广度优先,深度优先)