常见 VUE 面试题
1. 说说对 vue 的理解
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式 JavaScript 框架。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
2. 说说对 SPA 的理解
SPA(single-page application),翻译过来就是单页应用 SPA 是一种网络应用程序或网站的模型,它的特点在于整个应用只有一个完整的页面,点击页面中的导航菜单只会改变内容部分的内容。它是通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验在单页应用中,所有必要的代码(HTML、JavaScript 和 CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面页面在任何时间点都不会重新加载,也不会将控制转移到其他页面。
另一种网络应用程序模型是 MPA(multi-page application),多页应用,即多页面开发。多页面应用就是每次点击一个链接,就向服务器发送一次请求,服务器处理完数据之后,返回一个全新的页面。
单页应用与多页应用的区别
| 对比点 | 单页面应用(SPA) | 多页面应用(MPA) |
|---|---|---|
| 组成 | 一个主页面和多个页面片段 | 多个主页面 |
| 刷新方式 | 局部刷新 | 整页刷新 |
| url 模式 | 哈希模式&历史模式 | 历史模式 |
| SEO 搜索引擎优化 | 难实现,可使用 SSR/SSG 方式改善 | 容易实现 |
| 数据传递 | 容易 | 主要通过 Web API 如: url、cookie、localStorage 等传递 |
| 页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
| 维护成本 | 相对容易 | 相对复杂 |
由此可以看出 SPA 的优缺点:
优点:
- 具有桌面应用的即时性、网站的可移植性和可访问性
- 用户体验好、快,内容的改变不需要重新加载整个页面
- 对服务器压力较小
- 前后端分离,分工合作更加高效
- 开发成本低,维护成本低
缺点:
- 不利于搜索引擎的抓取
- 首次渲染速度相对较慢
- 页面跳转时,无法记录历史
如何给 SPA 做 SEO?
SEO(Search Engine Optimization)即搜索引擎优化,是指为了使网站在搜索结果中获得更高的排名,对网页内容进行优化的一种手段。
搜索引擎的工作原理是:搜索引擎通过爬虫程序(如百度、谷歌等)抓取互联网上的网页内容,然后对网页进行分析和排序,最终为用户提供相关的搜索结果。一般地,网站需要提供一些元数据(如标题、描述等)以及 sitemap.xml、robots.txt 等文件来告诉搜索引擎网页的主要内容和如何抓取网页内容。这些信息就是 SEO 的关键点。
那么为什么说 SPA 实现 SEO 困难呢?因为 SPA 的页面内容是通过 JavaScript 动态加载的,搜索引擎抓取页面内容时不会执行 JavaScript 代码,从而无法获取到动态加载的页面内容。因此,SPA 应用为了实现 SEO,需要使用 SSR(Server-Side Rendering)或 SSG(Static Site Generation)。SSR 和 SSG 是两种常见的 SEO 解决方案,它们可以将网页内容预先渲染为静态 HTML 文件,从而让搜索引擎能够抓取到页面内容,从而分析并收录网站内容。
除此外,SPA 还可以通过一些技术手段来改善 SEO:
- 使用 prerender 工具:prerender 是将 SPA 应用渲染为静态 HTML 文件的一种技术,它可以在服务器端执行 JavaScript 代码并获取到动态加载出来的页面内容,然后将渲染后的 HTML 文件返回给搜索引擎。如 prerender.io、prerender.cloud 等。
- 使用 headless browser:headless browser 是无界面浏览器,它可以在服务器端执行 JavaScript 代码并获取到动态加载出来的页面内容。如 Puppeteer、Phantomjs 等。
3. 说说对 MVVM 的理解
MVVM(Model-View-ViewModel)是一种软件架构设计模式,它通过将数据模型与视图分离来提高代码的可维护性和可测试性。MVVM 模式的核心是 ViewModel,它是视图模型,它负责将数据模型映射到视图层并处理用户交互事件。
MVVM 的优点:
- 职责清晰:MVVM 将应用程序分成三个主要部分:Model、View 和 ViewModel。Model 表示数据模型,View 表示视图,ViewModel 负责将 Model 和 View 连接起来,实现数据的绑定和更新。这样,每个部分都有明确的任务和职责,提高了代码的可维护性和可测试性。
- 视图和模型的解耦:MVVM 模式将视图和模型分离开来,使得视图可以独立于模型而存在,模型也可以独立于视图而存在。这使得视图和模型之间的耦合度降低,提高了代码的灵活性和可扩展性。
- 更好的可测试性:MVVM 模式将视图和模型分离开来,使得视图可以独立于模型而进行测试。这样,我们可以针对不同的模型编写不同的单元测试,从而提高程序的可测试性。
- 更好的可维护性:MVVM 模式将视图和模型分离开来,使得代码更加模块化和可重用。这样,我们可以轻松地替换视图或模型,而不会影响到其他部分。这使得代码更加容易维护和扩展。
MVVM 的缺点:
- 额外的开销:MVVM 模式需要额外的代码来实现数据绑定和事件处理,这会增加一些额外的开销。
- 性能问题:MVVM 模式需要额外的代码来实现数据绑定和事件处理,这可能会对性能产生一些影响。
- 缺乏官方支持:MVVM 模式并不是 JavaScript 语言的标准,也没有官方的库或框架来支持它。这可能会导致一些额外的学习成本和开发工作量。
- 增加程序的复杂性:MVVM 模式需要额外的代码来实现数据绑定和事件处理,这可能会增加程序的复杂性。
4. Vue 实例挂载的过程
Vue2 实例挂载的过程:
- 初始化配置:Vue 会对传入的选项进行解析和合并,包括数据、方法、生命周期钩子等。
- 初始化事件系统:Vue 会创建事件系统,用于处理组件之间的通信和交互。
- 初始化渲染:Vue 会根据模板和数据创建虚拟 DOM,并使用虚拟 DOM 进行首次渲染。
- 挂载实例:Vue 会将虚拟 DOM 渲染为真实 DOM,并将它插入到指定的容器元素中。
- 初始化数据绑定:Vue 会将数据和视图进行双向绑定,以便在数据发生变化时自动更新视图。
- 初始化生命周期钩子:Vue 会调用组件的 created、mounted 等生命周期钩子函数,以便在实例创建和挂载后执行一些额外的操作。
- 完成挂载:Vue 实例挂载完毕,组件已经完全渲染并插入到容器元素中。
Vue3 实例挂载的过程:
- 初始化配置:Vue3 和 Vue2 的初始化过程类似,但有一些细微的差异:Vue3 使用 Proxy 代替 Vue2 的 Object.defineProperty 进行数据绑定。
- 初始化事件系统:Vue3 和 Vue2 的事件系统类似,但有一些细微的差异:Vue3 使用 Proxy 进行事件监听和触发。
- 初始化渲染:Vue3 和 Vue2 的初始化过程类似,但有一些细微的差异:Vue3 使用 Proxy 进行数据监听和更新。并且 Vue3 使用异步渲染,以提高性能。
- 挂载实例:Vue 会将虚拟 DOM 渲染为真实 DOM,并将它插入到指定的容器元素中。
- 初始化数据绑定:Vue 会将数据和视图进行双向绑定,以便在数据发生变化时自动更新视图。
- 初始化生命周期钩子:Vue 会调用组件的 created、mounted 等生命周期钩子函数,以便在实例创建和挂载后执行一些额外的操作。
- 完成挂载:Vue 实例挂载完毕,组件已经完全渲染并插入到容器元素中。
5. 说说 vue 生命周期
Vue 实例的生命周期可以分为创建、挂载和销毁三个阶段。
- 创建阶段:在这个阶段,Vue 会执行一些初始化操作,例如合并选项、注册事件监听器等。
- 挂载阶段:在这个阶段,Vue 会将虚拟 DOM 渲染为真实 DOM,并将它插入到指定的容器元素中。
- 销毁阶段:在这个阶段,Vue 会清理一些资源,例如移除事件监听器、解绑数据等。
除了这三个阶段,还有一些其他的生命周期钩子函数,这些钩子函数可以在特定的阶段执行一些额外的操作或逻辑。
常见的钩子函数有:
- beforeCreate 在组件实例初始化完成并且 props 被解析后立即调用,接着 props 会被定义成响应式属性,data() 和 computed 等选项也开始进行处理。注意,组合式 API 中的 setup() 钩子会在所有选项式 API 钩子之前调用,beforeCreate() 也不例外。
- created 在组件实例处理完所有与状态相关的选项后调用。当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。
- beforeMount 在组件被挂载之前调用。当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。这个钩子在服务端渲染时不会被调用。
- mounted 在组件被挂载之后调用。组件在以下情况下被视为已挂载:所有同步子组件都已经被挂载。(不包含异步组件或
<Suspense>树内的组件);其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用,或是在服务端渲染应用中用于确保 DOM 相关代码仅在客户端被调用。这个钩子在服务端渲染时不会被调用。 - beforeUpdate 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。这个钩子在服务端渲染时不会被调用。
- updated 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。父组件的更新钩子将在其子组件的更新钩子之后调用。这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用
nextTick()作为替代。注意,这个钩子在服务端渲染时不会被调用。并且不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环! - beforeUnmount 在一个组件实例被卸载之前调用。当这个钩子被调用时,组件实例依然还保有全部的功能。这个钩子在服务端渲染时不会被调用。
- unmounted 在一个组件实例被卸载之后调用。一个组件在以下情况下被视为已卸载:其所有子组件都已经被卸载;所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止。可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。这个钩子在服务端渲染时不会被调用。
- errorCaptured 在捕获了后代组件传递的错误时调用。错误可以从以下几个来源中捕获:组件渲染、事件处理器、生命周期钩子、setup() 函数、侦听器、自定义指令钩子、过渡钩子等。这个钩子带有三个实参:错误对象、触发该错误的组件实例,以及一个说明错误来源类型的信息字符串。
- renderTracked 在一个响应式依赖被组件的渲染作用追踪后调用。仅用于调试。
- renderTriggered 在一个响应式依赖被组件触发了重新渲染之后调用。仅用于调试。
- activated 若组件实例是
<KeepAlive>缓存树的一部分,当组件被插入到 DOM 中时调用。这个钩子在服务端渲染时不会被调用。 - deactivated 若组件实例是
<KeepAlive>缓存树的一部分,当组件从 DOM 中被移除时调用。这个钩子在服务端渲染时不会被调用。 - serverPrefetch 当组件实例在服务器上被渲染之前要完成的异步函数。如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。这个钩子仅会在服务端渲染中执行,可以用于执行一些仅在服务端才有的数据抓取过程。
6. 说说 nextTick
nextTick 是一个 Vue 提供等待下一次 DOM 更新刷新的工具方法。
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
7. 说说 vue 的响应式原理
Vue 的响应式原理是数据劫持与发布订阅模式结合实现的。
Vue 的响应式主要包括下面几个方面:
数据劫持:
- Vue 的响应式系统是通过数据劫持来实现的。Vue 在初始化数据时,会使用
Object.defineProperty(Vue2) 对数据对象的属性进行劫持,为每个属性创建 getter 和 setter。
- Vue 的响应式系统是通过数据劫持来实现的。Vue 在初始化数据时,会使用
依赖收集:
- 当组件渲染时,Vue 会遍历组件的模板,找到所有的数据依赖,并将这些依赖“收集”起来。这个过程称为依赖收集。依赖收集是通过在 getter 中进行的,当属性被访问时,Vue 会记录下这个属性被哪个组件的模板所依赖。
依赖关系映射:
- Vue 内部会维护一个依赖关系映射,通常是一个数据结构(如 Map),它将每个数据属性与依赖于它的 watcher(观察者)关联起来。
异步更新队列:
- Vue 不会立即更新数据的变化,而是将所有数据变化放入一个异步队列。在同一事件循环中,如果多次更改数据,Vue 会将这些变化合并,从而避免了不必要的计算和 DOM 更新。
批量异步更新:
- 在下一个事件循环“tick”中,Vue 会清空队列,并执行唯一的一次 DOM 更新。这样可以避免不必要的 DOM 操作,提高性能。
setter 触发更新:
- 当数据变化时,setter 被调用,Vue 会通知所有依赖于这个数据的组件,让它们重新渲染。
计算属性和侦听器:
- Vue 提供了计算属性和侦听器,它们是基于响应式依赖的。计算属性是基于它们的响应式依赖进行缓存的,只有当依赖项发生变化时,计算属性才会重新计算。侦听器则允许你监听数据的变化,并在变化发生时执行特定的操作。
Vue 3 的响应式系统变化:
- Vue 3 使用了 Proxy 替代了 Vue 2 中的
Object.defineProperty,这使得 Vue 3 不仅可以劫持对象的属性,还可以劫持数组的变化,并且性能更好。
- Vue 3 使用了 Proxy 替代了 Vue 2 中的
8. 说说 vue 的模板编译过程
Vue 的模板编译过程是将 Vue 模板字符串转换成 JavaScript 渲染函数的过程。这个转换过程大致可以分为以下几个步骤:
解析(Parsing):
- 模板字符串被解析成一个抽象语法树(AST,Abstract Syntax Tree)。在这个步骤中,模板被拆解成各种组件,如元素、属性、指令、表达式等,并将这些组件以树状结构表示出来。
优化(Optimization):
- 在优化阶段,Vue 会对 AST 进行静态分析,以识别那些在渲染过程中不会改变的节点。例如,对于纯静态的节点,Vue 可以跳过依赖收集,因为它们不依赖于响应式数据。
代码生成(Code Generation):
- 根据优化后的 AST,生成可执行的 JavaScript 代码。这个过程会创建一个渲染函数,该函数能够生成虚拟 DOM 树。
渲染函数(Render Function):
- 渲染函数是编译过程的输出,它是一个 JavaScript 函数,接收一个上下文作为参数,并返回一个虚拟 DOM 树。这个函数可以被用来创建和更新组件的 DOM。
模板编译的具体步骤如下:
a. 词法分析(Lexical Analysis):
- 将模板字符串分解成一个个有意义的代码块,这些代码块包括 HTML 标签、指令(如
v-if、v-for)、表达式等。
b. 语法分析(Syntax Analysis):
- 将词法分析的结果转换成 AST。在这个阶段,Vue 会构建一个树状结构,每个节点代表了模板中的一个元素或指令。
c. 构建 AST:
- AST 的节点包含了足够的信息来描述模板的结构,例如节点类型、节点属性、子节点等。
d. 生成代码:
- 遍历 AST 并生成 JavaScript 代码。这个过程会根据 AST 中的节点生成相应的 JavaScript 表达式或语句。
e. 渲染函数的输出:
- 最终,编译器会输出一个渲染函数,这个函数能够根据组件的状态生成对应的虚拟 DOM。
f. 虚拟 DOM 的创建:
- 当组件的状态发生变化时,渲染函数会被重新调用,生成新的虚拟 DOM 树。
g. 差异比较和 DOM 更新:
- Vue 的响应式系统会监听数据的变化,并在变化发生时调用渲染函数来生成新的虚拟 DOM 树。然后,Vue 的虚拟 DOM 算法会比较新旧虚拟 DOM 树的差异,并最小化实际的 DOM 操作来更新视图。
Vue 模板编译过程的关键在于将声明式的模板转换为 JavaScript 代码,这样开发者就可以使用简洁的模板语法来构建复杂的用户界面,同时享受到虚拟 DOM 带来的性能优势。在 Vue 3 中,模板编译器和虚拟 DOM 的实现有了进一步的优化和改进,提高了性能和编译效率。
9. Vue 中组件和插件有什么区别?
Vue 组件和插件都是用于扩展 Vue 的功能或行为的方式。
组件是 Vue 中用于封装和复用代码的一种方式。它们可以包含自己的模板、数据、方法和生命周期钩子函数,以便于将功能模块化并重用。
插件则是一种全局性的扩展 Vue 的方式,可以添加全局功能、自定义指令或过滤器等。
两者的区别主要表现在以下几个方面:
- 作用范围:组件是可以是局部的,只能被使用在创建它的文件中或其子组件中;而插件则只能被全局地使用。
- 注册方式:组件需要使用
Vue.component()方法进行注册;而插件则可以使用Vue.use()方法进行全局注册。 - 生命周期钩子:组件有自己的生命周期钩子函数,如
created、mounted等;而插件则没有自己的生命周期钩子。 - 编写方式:编写一个组件,可以有很多方式,最常见的是 vue 单文件格式;编写一个插件,则只需要导出一个对象,并应该暴露一个 install 方法。
- 使用场景:组件适用于封装和复用代码,而插件则适用于添加全局功能或自定义指令等。
10. 说说 Vue 组件通信方式
Vue 组件通信是指在不同组件之间传递数据或消息的方式。Vue 提供了多种方式来实现组件之间的通信,场景包括父子组件通信、兄弟组件通信、跨级组件通信等。
1. 父子组件通信
父子组件通信是最常见的一种方式,父组件可以通过 props 向子组件传递数据,子组件通过事件向父组件发送消息。
props 是单向数据流,所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
2. 兄弟组件通信
兄弟组件通信可以借助一个中间事件中心来实现。在 Vue 中,可以使用 EventBus 来实现兄弟组件之间的通信。类似于订阅/发布模式,EventBus 是一个全局的事件中心,任何组件都可以向 EventBus 发送事件,任何组件都可以监听 EventBus 上发送的事件。
3. 跨级组件通信
跨级组件通信可以使用 透传 Attributes、事件总线、依赖注入 来实现。
透传 attribute 指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。那么,如果该组件是以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上,而当该组件在根节点上渲染的是另一个组件,那么 attribute 将会继续传递,这也叫做 深层组件继承。
依赖注入(provide/inject) 上面的方法存在一些问题,也就是当后代组件不是根组件时,无法自动传递 attribute,需要手动传递。针对这个问题,Vue 提供了依赖注入来解决。在祖先组件中通过 provide 来提供变量,然后在子孙组件中通过 inject 来注入变量。
4. 状态管理库
Vue 提供了多种状态管理库,如 Vuex、Pinia 等。这些状态管理库可以用来管理应用的状态,实现组件之间的通信和数据共享。Vuex 是 Vue 的官方状态管理库,它采用集中式存储管理应用的所有状态,并支持将状态映射到多个视图。Pinia 则是一种轻量级的替代方案,它使用基于函数的 API 来管理状态,并且不需要额外的配置或工具。
11. 说说 Vue 路由原理
12. 说说你对 vue 的 mixin 的理解,有什么应用场景?
Mixin 是 Vue 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。它允许开发人员将组件的通用功能提取到一个单独的对象中,然后在需要时将其合并到不同的组件中。
Mixin 是一个对象,它包含了一组可以在多个组件中复用的选项、方法和生命周期钩子。当一个组件需要使用这些选项和功能时,可以将 Mixin 对象合并到组件中,从而实现代码复用和功能扩展。
Mixin 有多种合并策略:
- 替换型:当存在 props、methods、inject、computed 选项同名时,同名选项会发生覆盖,多个 Mixin 以后来者会覆盖前者,组件的优先级高于 mixin。
- 合并型:当组件 data 对象不包含当前属性时,调用 set 方法进行合并(set 方法其实就是一些合并重新赋值的方法),当目标 data 对象包含当前属性并且当前值为纯对象时,递归合并当前对象值,这样做是为了防止对象存在新增属性。
- 队列型:生命周期钩子函数与 watch 会发生合并,会合并成一个数组,事件触发时会依次执行。
- 叠加型:Mixin 与组件的 component、directives、filters 会通过原型链进行层层叠加。
Mixin 的应用场景包括:
- 代码重用:Mixin 可以用于在多个组件之间共享代码和逻辑,减少重复代码并提高代码的可维护性。
- 可扩展性:Mixin 可以用于在组件中添加额外的功能或属性,以满足特定的需求。
- 可组合性:Mixin 可以与其他组件或插件进行组合,以创建更复杂的组件或应用程序。
13. 说说你对 vue 的插槽的理解,有什么应用场景?
插槽(Slot)是 Vue 提供的用于在组件模板中插入内容的功能。它允许开发人员将组件的特定部分与外部内容进行解耦,从而更好地控制组件的外观和行为。
Vue 插槽可以分为默认插槽 、 具名插槽 、作用域插槽。
默认插槽 默认插槽是组件模板中未指定名称的插槽,它会在组件渲染时显示外部内容。默认插槽通常用于在组件内部添加一些默认的内容或布局。
具名插槽 具名插槽是指组件模板中带有名称的插槽,它允许开发人员将外部内容插入到特定位置。具名插槽可以用于在不同场景下展示不同的内容,如列表项、表单元素等。
作用域插槽 作用域插槽是指组件模板中带有数据绑定和参数的插槽,它允许开发人员将外部内容与组件的数据进行绑定。作用域插槽可以用于在组件内部展示动态数据或自定义渲染逻辑。
在一些组件中使用插槽是个不错的选择,如列表组件、表单组件、下拉选、弹框显示内容等。
14. 说说你对 vue 的 computed 和 watch 的理解
相同点:
- 都是用来监听数据变化,并执行相应的操作。
- 都可以在数据变化时执行异步或开销较大的操作。
异同点:
- 计算属性(computed)是依赖其他数据计算的属性,它会在依赖的数据发生变化时自动更新。
- 侦听器(watch)是监听数据变化并执行相应操作的一种方式,它不会自动更新。
- 计算属性可以缓存计算结果,避免重复计算。
- 计算属性可以用于处理复杂逻辑,而侦听器则更适合监听单个数据。
- 计算属性支持异步操作,而侦听器不支持。
15. 说说对 vue 中 key 的理解
在 Vue 中,key 是用于标识虚拟 DOM 的唯一标识符。它是 diff 的一种优化策略,当执行更新时可以根据 key,更准确, 更快的找到对应的 vnode 节点。
无 key 和有 key 的区别:
- 如果使用 key,Vue 会根据 keys 的顺序记录 element,曾经拥有了 key 的 element 如果不再出现的话,会被直接 remove 或者 destoryed。
- 如果不用 key,Vue 会采用就地复地原则:最小化 element 的移动,并且会尝试尽最大程度在同适当的地方对相同类型的 element,做 patch 或者 reuse。
- 当拥有新值的 rerender 作为 key 时,拥有了新 key 的 Comp 出现了,那么旧 key Comp 会被移除,新 key Comp 触发渲染。
设置 key 值不一定能提高 diff 的效率。当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素,这种操作是高效的,不需要额外标记。
但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出,建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
16. 说说你对 keep-alive 的理解是什么?
keep-alive 是 Vue 提供的一个内置组件,用于缓存组件的输出内容,避免重复渲染。它可以在组件切换时保留组件的状态和数据,从而提高应用程序的性能和用户体验。
keep-alive 提供了三个属性:include 和 exclude,用于控制哪些组件可以被缓存,以及哪些组件应该被销毁。include 属性可以指定要缓存的组件名称或正则表达式,exclude 则相反,指定不希望缓存的组件名称或正则表达式。max 表示最多可以缓存多少组件实例。
当一个组件被 keep-alive 包裹时,它的生命周期钩子函数会受到一些影响:
- activated:当组件被缓存后,再次进入时会触发该钩子函数。
- unactivated:当组件被从缓存中移除时,触发该钩子函数。
17. Vue 常用的修饰符有哪些?
Vue.js 提供了多种修饰符来控制事件处理和 DOM 操作。以下是 Vue 中常用的修饰符:
- 事件修饰符:
.stop、.prevent、.capture、.self、.once、.passive、.native、.once。 - 按键修饰符:
.enter、.tab、.delete、.esc、.space、.up、.down。 - 鼠标修饰符:
.left、.right、.middle。 - 输入框修饰符:
.trim、.number、.lazy。 - v-bind 修饰符:
.camel、.sync。
18. 说说 Vue 的指令系统
Vue 的指令系统是 Vue 模板语法的一部分,通过声明式的方式将数据绑定到 DOM 元素或组件的属性上。Vue 提供了多种内置指令,指令一般地以 v- 开头,如 v-if、v-for、v-show、v-model 等,也可以自定义指令。
一般的指令可以分为三类:
- 属性指令:用于操作 DOM 元素的属性,如 v-bind、v-model 等。
- 事件指令:用于监听 DOM 事件的,如 v-on、v-click 等。
- 内容插入指令:用于将数据渲染到 DOM 元素或组件的内部,如 v-html、v-text 等。
指令的使用方式:
//会实例化一个指令,但这个指令没有参数
`v-xxx` // -- 将值传到指令中
`v-xxx="value"` // -- 将字符串传入到指令中,如`v-html="'<p>内容</p>'"`
`v-xxx="'string'"` // -- 传参数(`arg`),如`v-bind:class="className"`
`v-xxx:arg="value"` // -- 使用修饰符(`modifier`)
`v-xxx:arg.modifier="value"`;通过 directive API 也可以自定义全局与局部指令。
自定义指令也像组件那样存在钩子函数:
bind:指令第一次绑定到元素时调用,只调用一次。inserted:被绑定元素插入父节点时调用。update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。componentUpdated:所在组件的 VNode 及其子 VNode 全部更新后调用。unbind:指令与元素解绑时调用,只调用一次。
19. Vue 中的过滤器了解吗?过滤器的应用场景有哪些?
Vue 中的过滤器是一种用于格式化数据的方法,它可以在模板中调用,将原始数据转换为格式化的结果。过滤器可以用于格式化日期、数字、字符串等常见的数据类型,也可以用于自定义的格式转换逻辑。
vue 中的过滤器可以用在两个地方:双花括号插值和 v-bind 表达式,过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>然后通过 filters 属性来定义过滤器。
过滤器的应用场景包括:
- 格式化数据:使用过滤器可以将原始数据格式化为指定的格式,如日期、时间、货币等。
- 数据转换:过滤器还可以用于对数据进行转换或处理,如将字符串转换为大写、小写、首字母大写等。
- 自定义格式:开发人员可以根据自己的需求定义自定义的格式转换逻辑,以满足特定的格式化要求。
- 复用性:过滤器可以被多个组件共享和重用,减少代码冗余并提高代码的可维护性。
20. 什么是虚拟 DOM?如何实现一个虚拟 DOM?
虚拟 DOM(Virtual DOM)是一种用于描述真实 DOM 的轻量级 JavaScript 对象,它通过将真实 DOM 的状态映射到虚拟 DOM 中,来模拟真实的 DOM 结构,从而实现了对 DOM 的高效操作。
虚拟 DOM 通过将表现层和数据层分离,实现了对 DOM 的抽象和封装,使得开发者可以更加方便地操作 DOM 元素。并且可以基于虚拟 DOM 实现不同平台的渲染,从而实现跨平台开发,如 Web 应用程序、移动应用程序和桌面应用程序等。
实现一个虚拟 DOM 需要以下几个步骤:
- 创建虚拟节点:在 JavaScript 中创建一个虚拟节点对象,包含节点的类型、属性、子节点等属性。
- 渲染虚拟节点:根据虚拟节点对象的内容生成真实的 DOM 元素,并将其添加到页面上。
- 比较更新虚拟节点:当需要更新 DOM 时,比较新旧虚拟节点之间的差异,并只更新变化的部分,避免不必要的 DOM 操作。
- 批量更新 DOM:将所有更新的 DOM 操作合并在一起,以提高性能和效率。
21. 说说 vue 的 diff 算法
Vue 的 diff 算法是 Vue 中用于比较新旧虚拟 DOM 树并更新真实 DOM 的过程。diff 算法的核心思想是尽可能减少 DOM 操作,以实现高效、稳定的更新视图。
Vue 的 diff 算法采用了双端比较和三向归并的策略:
- 双端比较:将新旧虚拟 DOM 树的节点进行比较,找出差异点。
- 三向归并:将相同节点的子节点进行比较,如果子节点相同则继续递归比较,否则使用 key 值进行比较。
- 标记删除:标记需要删除的节点,以便在后续的批量更新中将其从 DOM 中移除。
- 标记新增:标记需要添加的新节点,以便在后续的批量更新中将其插入到 DOM 中。
- 标记修改:标记需要更新的节点,以便在后续的批量更新中对其进行更新。
- 批量更新:将所有需要更新的节点一次性更新到 DOM 中,以提高性能和效率。
diff 算法大概流程如下:
- 判断是否存在新旧节点,没有旧节点则直接创建新节点并插入到 DOM 中,没有新节点则直接销毁旧节点。都存在判断是否是相同节点,不是则替换旧节点为新节点。
- 新旧节点是否存在子节点,都存在则进行递归 diff,没有子节点则比较文本内容是否相同。
- 采用双端比较和三向归并策略,找出差异点并进行标记。
- 批量更新 DOM,将需要更新的节点一次性更新到 DOM 中。
22. vue 要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源
而前端权限归根结底是请求的发起权,请求的发起可能有下面两种形式触发
- 页面加载触发
- 页面上的按钮点击触发
总的来说,所有的请求发起都触发自前端路由或视图
所以我们可以从这两方面入手,对触发权限的源头进行控制,最终要实现的目标是:
- 路由方面,用户登录后只能看到自己有权访问的导航菜单,也只能访问自己有权访问的路由地址,否则将跳转 4xx 提示页
- 视图方面,用户只能看到自己有权浏览的内容和有权操作的控件
- 最后再加上请求控制作为最后一道防线,路由可能配置失误,按钮可能忘了加权限,这种时候请求控制可以用来兜底,越权请求将在前端被拦截
前端权限控制可以分为四个方面:
- 接口权限:接口权限目前一般采用 jwt 的形式来验证,没有通过的话一般返回 401,跳转到登录页面重新进行登录;登录完拿到 token,将 token 存起来,通过 axios 请求拦截器进行拦截,每次请求的时候头部携带 token。
- 按钮权限:通过 v-if 判断按钮是否显示,如果按钮没有权限则不显示,或者通过自定义指令 v-hasPermission 判断按钮是否显示,如果按钮没有权限则不显示。
- 菜单权限:通过路由的 addroutes 方法动态添加路由,如果用户没有权限则不添加该路由。
- 路由权限:通过路由守卫进行拦截,判断用户是否有该路由访问权限,没有则跳转到 403 页面。
23. Vue 项目中你是如何解决跨域的呢?
跨域本质是浏览器基于同源策略的一种安全手段。
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能。
所谓同源(即指在同一个域)具有以下三个相同点:
- 协议相同(protocol)
- 主机相同(host)
- 端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域。
Vue 项目中解决跨域的方式有很多,常用的有以下几种:
- 使用代理服务器:在 Vue 的配置文件中(通常是
vue.config.js)设置代理服务器,将目标服务器的请求代理到本地服务器上,从而实现跨域访问。 - 使用 CORS 跨域资源共享:在目标服务器上设置 Access-Control-Allow-Origin 头文件,允许来自指定域名的请求访问该资源。
- JSONP:通过动态创建 script 标签,实现跨域请求。
24. vue 项目本地开发完成后部署到服务器后报 404 是什么原因呢?
Vue 项目部署到服务器后报 404 错误,通常是由于服务器配置不当或 Vue 的路由模式导致的。
- 服务器配置问题:如果使用的是 Nginx 等 Web 服务器,需要配置静态资源路径和默认页面,否则无法正确加载 Vue 应用程序的静态文件。
- Vue 的路由模式:在开发环境中,Vue 使用 hash 模式的路由,而生产环境需要使用 history 模式的路由。如果不进行相应的配置,会导致路由切换时出现 404 错误。通过配置将任意页面都重定向到 index.html,把路由交由前端处理解决问题。
25. Vue3 使用过程中有遇到什么问题吗?
在使用 Vue 3 开发过程中,开发者可能会遇到各种问题。以下是一些常见的问题及其解决方案:
使用
reactive封装基础数据类型:- Vue 3 中,
reactive用于封装对象、数组、Map、Set数据类型,而ref用于封装基础数据类型如String、Number、Boolean。如果使用reactive来封装基础数据类型,会产生警告,且封装的值不会成为响应式对象。
- Vue 3 中,
解构
reactive对象:- 不能直接解构
reactive对象,因为这会导致响应式引用丢失。如果需要解构,应该使用toRefs方法来保持响应性。
- 不能直接解构
使用
.value造成的困惑:ref接受一个值并返回一个响应式对象,该值在内部对象的.value属性下可用。在模板中使用ref时,不需要对ref进行解包,即不需要使用.value。但在 JavaScript 中使用时,需要通过.value访问或修改值。
Emitted事件:- 在 Vue 3 中,子组件与父组件通信的事件必须通过
defineEmits来声明。此外,.native修饰符已被移除,因为事件现在必须声明。
- 在 Vue 3 中,子组件与父组件通信的事件必须通过
声明组件选项:
setup函数不支持声明某些组件选项,如name、inheritAttrs、customOptions。如果需要使用这些属性,可以声明多个<script>标签。
使用
Reactivity Transform:Reactivity Transform是 Vue 3 中的一个实验性特性,它旨在简化组件的声明方式,通过编译时转换自动解包ref,避免使用.value。但从 Vue 3.3 开始,该功能已被移除,作为一个扩展包提供。
定义异步组件:
- Vue 3 中定义异步组件需要使用
defineAsyncComponent,而不是将它们包含在方法中。
- Vue 3 中定义异步组件需要使用
template中使用不必要的包装元素:- Vue 3 支持多个根元素,因此不再需要使用外层
<div>元素包裹。
- Vue 3 支持多个根元素,因此不再需要使用外层
生命周期函数:
- 所有组件生命周期函数都通过添加
on前缀或完全更名实现,例如beforeCreate变为onBeforeMount。
- 所有组件生命周期函数都通过添加
产品文档:
- 官方文档已经更新,补充了 API 和许多有价值的注释、指南和最佳实践。即使是 Vue 2 的用户,通过阅读新的文档也能学到新知识。
这些问题和解决方案可以帮助开发者在 Vue 3 的使用过程中避免一些常见的陷阱。如果遇到其他问题,可以查阅官方文档或社区讨论以获得帮助。
26. 说说 Vue3.0 的设计目标是什么?做了哪些优化?
Vue3.0 的设计目标是提供更好的性能、更小的体积和更好的兼容性。
Vue2 虽然也提供了很多优化,但随着 Vue2.x 版本的发展,一些问题也逐渐暴露出来。比如:
- 随着功能的增长,复杂组件的代码变得越来越难以维护;
- 缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制;
- Vue2.x 版本对 IE11 及以下浏览器的支持不够友好,导致 Vue2.x 版本的体积较大。
- Vue2.x 版本对 TypeScript 的支持不够友好,导致开发人员需要使用 Babel 进行转译。
- 编译速度较慢,导致 Vue2.x 版本的应用程序启动时间较长。
以下是 Vue3.0 的一些优化:
- 编译速度更快:Vue3.0 使用 Rollup 打包工具,并引入了新的编译器,可以更快地解析和编译模板。
- 体积更小:Vue3.0 删除了一些不必要的代码,如废弃的 API 和特性,从而减小了包的体积。
- 更好的兼容性:Vue3.0 支持更多的浏览器和平台,包括 IE11 及以上版本和现代浏览器。
- 更好的 TypeScript 支持:Vue3.0 提供了更好的 TypeScript 支持,使得开发人员可以使用 TypeScript 编写 Vue 应用程序。
- 新的 API:Vue3.0 引入了一些新的 API,如
setup()函数、ref()和reactive()等。这些 API 使开发人员可以更轻松地编写 Vue 应用程序,以及更好地控制应用程序的状态和行为。 - 更好的性能:Vue3.0 采用了新的渲染算法,可以更快地更新 DOM 并减少内存使用。
- 开放更多底层功能:Vue3.0 开放了更多的底层功能,如编译器和渲染器等。这使得开发人员可以更深入地控制 Vue 应用程序的行为和性能。
- 更好的开发者体验:Vue3.0 引入了新的开发工具,如 Vite 和 Vue CLI,可以更快地启动项目并更好地调试应用程序。
27. Vue3 的 Composition API 和 React Hooks 有哪些区别?
Vue3 的 Composition API 和 React Hooks 都是用于在 Vue 或 React 中编写组件逻辑的函数式编程范式。它们之间有一些相似之处,但也存在一些差异:
- 作用域:Composition API 使用
setup()函数来定义组件逻辑,而 React Hooks 则使用函数组件。 - 状态管理:Composition API 使用
ref()和reactive()来管理组件的状态,而 React Hooks 则使用useState()和useEffect()来管理状态和副作用。 - 生命周期:Composition API 使用
onMounted()和onUnmounted()等钩子函数来管理组件的生命周期,而 React Hooks 则使用useEffect()来处理副作用,并使用useState()来管理状态。
28. Vue3.0 里为什么要用 Proxy API 替代 defineProperty API ?
Vue3.0 使用 Proxy API 代替 defineProperty API 是为了提高性能和灵活性。
在 Vue2.x 中,defineProperty API 被用来监听数据的变化并更新视图。但是它存在一些问题,比如需要递归遍历所有属性,导致性能下降;同时,defineProperty API 无法监听数组长度变化、对象属性的添加和删除等操作。
而在 Vue3.0 中,使用 Proxy API 可以直接代理对象,并且可以拦截更多操作,如数组长度变化、对象的 get 和 set 等操作。这样就可以更好地管理状态,从而提高性能和灵活性。
此外,Proxy API 还提供了更好的类型支持、更简洁的语法和更好的调试体验。因此,Vue3.0 选择了使用 Proxy API 来替代 defineProperty API。
29. 说说 Vue 3.0 中 Treeshaking 特性?举例说明一下?
Vue 3.0 中的 Treeshaking 特性是用来优化打包体积和性能的一种技术。它允许 Webpack 等打包工具删除未使用的代码,从而减小打包后的文件大小,提高加载速度和用户体验。
简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码
如果把代码打包比作制作蛋糕,传统的方式是把鸡蛋(带壳)全部丢进去搅拌,然后放入烤箱,最后把(没有用的)蛋壳全部挑选并剔除出去
而 treeshaking 则是一开始就把有用的蛋白蛋黄(import)放入搅拌,最后直接作出蛋糕
也就是说 ,tree shaking 其实是找出使用的代码
在 Vue2 中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是 Vue 实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到。
而 Vue3 源码引入 tree shaking 特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中。
Tree shaking 是基于 ES6 模板语法(import 与 exports),主要是借助 ES6 模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量
Tree shaking 无非就是做了两件事:
- 编译阶段利用 ES6 Module 判断哪些模块已经加载
- 判断那些模块和变量未被使用或者引用,进而删除对应代码
30. 如何封装一个公共组件?需要考虑哪些问题?
在设计上应该遵循以下原则:
- 可复用性:组件应该能够被多个项目或应用程序重用。
- 可定制性:组件应该能够被多个项目或应用程序进行定制,以满足不同的需求和风格。
- 可测试性:组件应该具有足够的单元测试和端到端测试,以确保其正确性和稳定性。
- 可维护性:组件应该易于理解和修改,以适应不断变化的需求和需求。
代码组织上需要考虑以下几个方面:
- 命名规范:组件的名称应该具有描述性,能够清晰地表达组件的功能和用途。
- 代码结构:组件应该包含必要的文件和文件夹结构,如
src目录、components目录等。 - Props 和事件:组件应该接受自定义属性(props)和触发自定义事件(events)。
- 样式:组件应该具有可定制化的样式,包括主题、尺寸、颜色等。
- 文档:组件应该提供详细的文档说明,包括使用方法、参数说明、示例代码等。
- 测试:组件应该编写单元测试和端到端测试来确保其正确性和稳定性。
- 发布:将组件发布到 npm 或其他包管理器中,以便其他人可以轻松地使用和更新。
- 维护:组件应该具有清晰的版本控制和更新日志,以便用户能够跟踪组件的最新版本和更改。
- 示例:提供组件的示例代码,以便用户可以快速了解如何使用和定制组件。