面试经典题

作者: shaokang 时间: April 13, 2023字数:7082

解释一下什么是闭包,它的作用是什么?

闭包是指一个函数能够访问并操作其外部函数的变量,即使这个外部函数已经执行完毕并被销毁。闭包常常用于实现封装性、模块化和数据私有化等特性。其特点是数据持久化、私有变量管理和实现模块化开发。

什么是事件委托?它有什么好处?

事件委托是将子节点的事件委托到其父节点或者更高层级的节点上。主要优点:减少事件绑定次数,提高页面性能;动态绑定事件(当子元素动态生成或者经常变化,不用重新绑定事件);代码简洁易懂。

请解释一下什么是 Event Loop(事件循环),以及它在 JavaScript 异步编程中的作用。

Event Loop(事件循环)是 JavaScript 中一种用于管理异步执行的机制。在 JavaScript 中,任务分为两种类型:宏任务和微任务。宏任务包括 setTimeout、setInterval、setImmediate 等异步 API,而微任务包括 Promise、MutationObserver 等。

当 JavaScript 代码执行时,它会按照同步的方式执行主线程上的任务。但是,当遇到异步任务时,会将它们放到对应的任务队列中,等待异步任务执行完毕后再执行回调函数。当主线程上的任务执行完毕后,JavaScript 引擎会查找微任务队列中是否有任务需要执行,如果有,则一次性将所有微任务执行完毕,然后再查找宏任务队列中是否有任务需要执行。这个过程不断循环,被称为事件循环。

Event Loop 的作用在于,通过任务队列机制实现了 JavaScript 异步编程,使得 JavaScript 代码可以通过回调函数等方式处理异步操作,从而不会阻塞主线程的执行。通过合理的使用异步任务和事件循环机制,可以提高 JavaScript 程序的性能和响应速度。

请列举你在前端开发中常用的 CSS 预处理器,并说明它们的优点。

Sass(Syntactically Awesome Style Sheets): Sass 是目前使用最广泛的 CSS 预处理器之一,提供了许多方便的特性,如变量、嵌套、混合、继承等。同时,Sass 也提供了两种语法格式:Sass 和 SCSS。

Less:Less 是一种动态样式语言,与 Sass 类似,它也提供了类似变量、嵌套、混合、继承等功能。Less 的语法比 Sass 更加简单,学习起来较为容易(变量定义更简单、嵌套的形式更清晰、操作符简单)。

Stylus:Stylus 是一种极简的 CSS 预处理器,它的语法非常简洁,许多代码都可以省略。它的特点是非常灵活(可选的分号、大括号、灵活定义变量),支持函数式编程、动态计算等高级特性。

如何使用 CSS 实现垂直居中?

  1. 使用 flex 布局:设置align-items: center
  2. 使用绝对定位:设置负边距或者transform: translateY(-50%)实现垂直居中;
  3. 使用 table 和 table-cell。
  4. 使用 line-height 等于父元素高度的方式实现垂直居中(行内元素)。

请描述一下什么是 AJAX,以及它的优缺点。

Ajax(Asynchronous JavaScript and XML)指的是一种在不刷新页面的情况下,通过 JavaScript 异步发起 HTTP 请求并获取响应的技术。

优点:不刷新页面情况下更新数据。异步发起请求,提高响应速度。较少数据传输量(不必获取整个页面的 HTML 文件)。 缺点:兼容性。不能处理跨域(CORS、JSONP)。安全问题 XSS(CSP、过滤转义)、CSRF(Token、SameSite Cookie)。

请问你了解红黑树吗?如果了解,可以简单介绍一下它的特点和应用场景吗?

红黑树是一种自平衡的二叉查找树,它的特点包括:

  • 节点只有红色和黑色两种颜色;
  • 根节点和叶子节点(空节点)是黑色的;
  • 红色节点的父节点和子节点都是黑色的;
  • 从任意一个节点到其叶子节点的所有路径都包含相同数目的黑色节点;
  • 插入、删除等操作能保证树的平衡,最长路径不超过最短路径的两倍。 红黑树的应用场景很多,比如在 STL 中的 set、map、multiset、multimap 等容器就是基于红黑树实现的。因为红黑树具有自平衡的特性,所以在需要频繁插入、删除、查找元素的情况下,红黑树能够保证高效地维护数据结构的平衡,具有很好的时间复杂度表现。此外,红黑树还可以应用于文件系统、数据库锁等领域。

请解释一下什么是跨域问题,并举例说明常见的跨域解决方案。

跨域问题是由于浏览器的同源策略导致的。同源策略要求浏览器只能向同源网站发起请求,而同源是指协议、域名、端口号完全一致。

当浏览器检测到请求不符合同源策略时,就会拒绝响应请求,从而导致跨域问题。常见的跨域问题解决方案包括:

  1. JSONP:利用 <script> 标签可以跨域访问的特性,将数据封装在回调函数中返回给前端。
  2. CORS:服务端设置相应的响应头来允许跨域请求,前端可以正常访问。
  3. 代理:在同源策略下,使用服务器端作为中转,将前端请求发送到服务器端,服务器端再转发到目标地址,从而避免跨域问题。
  4. postMessage:HTML5 新增的跨文档通信机制,可以实现跨域通信。
  5. WebSocket:HTML5 新增的协议,可以实现客户端和服务器之间的双向通信,避免跨域问题。

请问你熟悉 Vue.js 吗?如果熟悉,你能说一下 Vue.js 的生命周期钩子吗?

beforeCreate:实例刚被创建,组件属性计算之前,如 data、methods 等都还未初始化。 created:实例创建完成,可以访问 data、methods 等属性,但是还没有挂载到 DOM 上。 beforeMount:模板编译完成,但是还没有挂载到 DOM 上。 mounted:实例挂载到 DOM 上,此时可以访问到 DOM 元素。 beforeUpdate:响应式数据更新时调用,但是 DOM 尚未更新。 updated:响应式数据更新且 DOM 已经更新完毕。 beforeDestroy:实例销毁之前调用,此时实例仍然可以访问,但是一些引用关系已经解除,比如移除事件监听等。 destroyed:实例被销毁后调用,此时所有的事件监听与子实例也已经被销毁。

React 中,什么是虚拟 DOM(Virtual DOM)?它的作用是什么?

虚拟 DOM(Virtual DOM)是一种用 JavaScript 对象表示真实 DOM 的方式,通过在内存中操作虚拟 DOM 来最小化页面渲染的次数,提高页面渲染的性能。它的作用包括:

  1. 兼容性:虚拟 DOM 可以屏蔽底层平台的差异,让开发者更加关注业务逻辑和用户交互,而不用担心不同浏览器之间的兼容性问题。
  2. 跨平台:虚拟 DOM 可以在不同的平台(如浏览器、服务器、移动端等)上运行,方便开发者进行跨平台开发。
  3. 性能优化:虚拟 DOM 可以通过 diff 算法来最小化页面渲染的次数,从而提高页面渲染的性能。它可以在内存中对 DOM 树进行操作,通过比对新旧虚拟 DOM 树的差异,计算出最小化的 DOM 更新操作,然后将这些操作批量更新到浏览器的实际 DOM 中,减少不必要的重绘和回流,提高页面的渲染效率。

优缺点: 虚拟 DOM 的优点包括:

性能优化:虚拟 DOM 通过在 JavaScript 中操作虚拟 DOM 树,减少对真实 DOM 的频繁访问和操作。通过比较两个虚拟 DOM 的差异,只对需要更新的部分进行真实 DOM 的更新,减少了不必要的重绘和回流,提高了性能。

跨平台能力:虚拟 DOM 可以在不同平台上使用相同的接口进行操作,无论是浏览器环境还是其他环境(如服务器端渲染、移动端开发等),开发者可以使用相同的代码和思维方式进行开发。

开发效率:虚拟 DOM 通过提供一种声明式的编程方式,将前端开发从直接操作 DOM 转变为操作虚拟 DOM,简化了开发过程,提高了开发效率。同时,虚拟 DOM 也提供了丰富的工具和库来辅助开发,如组件化、状态管理等。

虚拟 DOM 的缺点包括:

内存消耗:虚拟 DOM 需要在内存中维护一份与真实 DOM 相对应的虚拟 DOM 树,这可能会导致额外的内存消耗。尤其在大型应用中,虚拟 DOM 的内存占用可能会成为一个问题。

学习成本:虚拟 DOM 是一种抽象的概念,需要开发者理解其原理和使用方式。相比直接操作真实 DOM,使用虚拟 DOM 需要学习一些新的概念和工具,对于初学者或已经熟悉传统 DOM 操作的开发者来说,可能需要花费一些时间来适应和学习。

一些特定场景的效率问题:虚拟 DOM 在大多数情况下能够提供良好的性能,但对于某些特定场景,如频繁的大量节点操作,虚拟 DOM 可能不如直接操作真实 DOM 效率高。在这种情况下,可以考虑使用原生 DOM 操作或其他优化方式。

vue 和 react 在虚拟 dom 的 diff 上,做了哪些改进使得速度很快?

Vue 和 React 都采用了虚拟 DOM 的 diff 算法来提高性能,并在算法上做了一些改进以加快 diff 过程的速度。下面是 Vue 和 React 在虚拟 DOM 的 diff 上的一些改进:

差异化比较:Vue 和 React 在进行虚拟 DOM 的 diff 时,不会直接比较整个虚拟 DOM 树的所有节点,而是通过一种算法只比较具有相同父节点的同级节点。这样可以大大减少需要比较的节点数量,提高 diff 的速度。

列表算法优化:在处理列表数据时,Vue 和 React 都使用了类似的算法来提高 diff 的效率。例如,Vue 使用了”key”属性来标识列表中的每个节点,通过 key 的唯一性,可以更快地识别出新增、删除和移动的节点,避免了对整个列表进行全量比较。

组件级别的 diff:Vue 和 React 都支持组件化开发,它们在进行虚拟 DOM 的 diff 时,会对组件进行更精确的比较。如果组件的 props 或状态没有发生变化,就不会对该组件进行 diff,从而避免不必要的更新操作,提高性能。

批量异步更新:Vue 和 React 都采用了批量异步更新的机制,将多次 DOM 更新操作合并成一次,从而减少了触发浏览器重绘的次数,提高了性能。Vue 使用了”异步更新队列”的概念,通过 nextTick 将 DOM 更新推迟到下一个事件循环中执行;React 使用了批量更新机制,将多次 setState 的调用合并成一次更新。

虚拟 DOM 树的优化:Vue 和 React 都对虚拟 DOM 树进行了一些优化,以减少内存消耗和 diff 过程中的计算量。例如,Vue 使用了基于 ES6 的 Proxy 对象来实现虚拟 DOM 树的响应式更新,避免了对整个虚拟 DOM 树的遍历。React 则使用了 Fiber 架构来对虚拟 DOM 树进行异步的增量更新,使得更新过程更加细粒度和可控。

这些改进使得 Vue 和 React 在虚拟 DOM 的 diff 过程中能够更快速地计算出差异,并且减少了对真实 DOM 的操作,提高了应用的性能。

vue 和 react 里的 key 的作用是什么? 为什么不能用 Index?用了会怎样? 如果不加 key 会怎样?

在 Vue 和 React 中,key 属性用于标识列表中的每个节点,它的作用是帮助框架准确地追踪每个节点的身份,并在列表发生变化时进行高效的更新。使用 key 可以提供以下好处:

节点身份追踪:通过 key 属性,Vue 和 React 可以识别每个节点的唯一标识,从而在进行虚拟 DOM 的 diff 算法时,能够准确判断哪些节点是新增的、哪些是被删除的、哪些是被移动的,而不是简单地根据节点的位置进行比较。

优化列表操作:当列表数据发生变化时,如果没有使用 key 来标识节点,框架只能按照节点在列表中的位置进行比较。这意味着当插入、删除或移动节点时,框架需要对整个列表进行全量比较,导致性能下降。而使用 key 后,框架可以更快速地识别出新增、删除和移动的节点,只对需要更新的部分进行 diff,减少了不必要的计算量。

为什么不能使用索引(Index)作为 key 呢?

使用索引作为 key 会导致一些问题,尤其是在列表发生变化时。如果使用索引作为 key,当插入或删除节点时,节点的位置会发生变化,导致索引也会变化,这可能会导致框架错误地认为某个节点是被移动而不是被删除或新增。这会导致一系列问题,如错误的节点更新、错误的动画效果等。

如果不加 key 会怎样?

如果没有提供 key 属性,框架将默认使用节点在列表中的索引作为 key。这在一些简单的静态列表中可能不会引起问题,但在动态列表中,特别是在涉及节点插入、删除或移动的情况下,没有明确的 key 会导致框架无法正确追踪和更新节点的身份。这可能会导致一些奇怪的 bug 和不稳定的行为,因此,推荐在动态列表中始终使用唯一的 key 属性来标识节点。

vue 的 keep-alive 的作用是什么?怎么实现的?如何刷新的?

<keep-alive> 是 Vue 中的一个内置组件,用于缓存动态组件。它的主要作用是在组件切换时将组件的状态保留在内存中,而不是销毁和重新创建组件,从而提高性能并实现组件的复用。

具体作用如下:

组件缓存:使用 <keep-alive> 包裹的组件会被缓存到内存中,不会被销毁。这样,当组件被切换出去后再切换回来,组件的状态、数据以及 DOM 结构都会保留,避免了重新渲染和初始化的开销,提高了页面的响应速度。

组件复用:由于组件被缓存,切换回来时会直接从内存中取出缓存的组件,而不是重新创建一个新的组件实例。这样可以保持组件的状态和数据不变,实现组件的复用,适用于一些需要保持状态的场景,如表单填写、步骤导航等。

<keep-alive> 的实现方式是通过 Vue 提供的抽象组件 AbstractComponent 来实现的,它在内部使用一个缓存对象来存储被缓存的组件实例。当组件被切换到 <keep-alive> 包裹的容器内时,组件的实例会被缓存到缓存对象中。当组件切换到其他地方时,组件实例会被移除或禁用,但不会销毁。

刷新 <keep-alive> 中的组件可以通过以下方式实现:

  • 使用 include 和 exclude 属性:可以在 <keep-alive> 上使用 include 和 exclude 属性来指定哪些组件需要被缓存或排除在缓存之外。当指定的组件发生变化时,会触发刷新,即重新创建组件实例。
  • 使用 key 属性:在使用 <keep-alive> 包裹的组件上添加 key 属性,每当 key 发生变化时,会触发刷新。通过在组件切换时改变 key 值,可以强制重新创建组件实例。

vue 是怎么解析 template 的? template 会变成什么?

Vue 在解析和编译模板时,会经过以下几个步骤:

  1. 模板解析:Vue 会将模板字符串解析成抽象语法树(AST,Abstract Syntax Tree)。这个过程包括词法分析(将模板字符串转换为词法单元)和语法分析(构建抽象语法树)。
  2. 静态分析:在静态分析阶段,Vue 会遍历抽象语法树,并收集模板中的静态内容,如静态节点和静态属性。这些静态内容在每次渲染时都不会变化,可以被提前标记和优化。
  3. 编译优化:在编译优化阶段,Vue 会对抽象语法树进行优化处理,包括静态节点提升(将静态节点提升为常量,减少渲染时的计算量)、静态属性提升(将静态属性缓存起来,避免重复计算)等。
  4. 生成渲染函数:最后,Vue 会将优化后的抽象语法树转换为可执行的渲染函数。渲染函数是一个 JavaScript 函数,接收数据作为参数,然后生成虚拟 DOM 节点,最终通过虚拟 DOM 的 diff 算法更新真实 DOM。 在模板被解析和编译后,最终会转换成一个渲染函数。

TS 中的一些类型工具类

  • Partial:将 T 中所有属性设置为可选。
  • Required:将 T 中所有属性设置为必需。
  • Pick<T, K>:从 T 中选择指定的属性 K 并构建一个新类型。
  • Omit<T, K>:从 T 中省略指定的属性 K 并构建一个新类型。
  • Record<K, T>:创建一个具有属性 K 的对象,并且该对象的所有属性都具有类型 T。
  • Exclude<T, U>:从 T 中排除所有类型 U。
  • Extract<T, U>:从 T 中提取所有类型 U。
  • NonNullable:从 T 中排除 null 和 undefined。
  • ReturnType:获取函数类型 T 的返回类型。
  • Parameters:获取函数类型 T 的参数类型元组。
  • ConstructorParameters:获取构造函数类型 T 的参数类型元组。
  • ThisParameterType:获取函数类型 T 的 this 参数类型。
  • OmitThisParameter:从函数类型 T 中省略 this 参数。