1、虚拟 DOM 中 key 的作用:
key 是虚拟 DOM 对象的标识,当状态中的数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后 Vue 进行【新虚拟 DOM】的差异比较,比较规则如下:
2、key 的对比规则:
1、旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:若虚拟 DOM 中内容没变,直接使用之前的真实 DOM 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
2、旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key 创建新的真实 DOM,随后渲染到页面
3、用 index 作为 key 可能会引发的问题:
1、若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实 DOM 更新 ===> 界面效果没问底,但效率低
2、如果结构中还包含输入类的 DOM:会产生错误 DOM 更新 ===> 界面有问题
4、开发中如何选择 key?
1、最好使用每条数据的唯一标识作为 key,比如 id、手机号、身份证号、学号等唯一值
2、如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的
5、Vue 组件中 data 为什么必须是函数?
保证组件的独立性和可复用性,data 是一个函数,组件实例化的时候将会调用这个函数,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次,就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
6、vuex 的 State 特性是?
state 属性是 Vuex 中用于存放组件之间共享的数据;也就是说,我们把一些组件之间共享的状态主要存放在 state 属性中;它采用的是单一状态树——用一个对象就包含了全部的应用层级状态。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
7、Vue 声明组件的 state 是⽤ data ⽅法,那为什么 data 是通过⼀个 function 来返回⼀个对象,⽽不是直接写⼀个对象呢?
从语法上说,如果不⽤ function 返回就会出现语法错误导致编译不通过。从原理上的话,⼤概就是组件可以被多次创建,如果不使⽤ function 就会使所有调⽤该组件的页⾯公⽤同⼀个数据域,这样就失去了组件的概念了
8、介绍一下 Vue 的响应式系统
1、任何一个 Vue ***ponent 都有一个与之对应的 Watcher 实例
2、Vue 的 data 上的属性会被添加 getter 和 setter 属性
3、当 Vue ***ponent render 函数被执行的时候, data 上会被 触碰 (touch), 即被读, getter 方法会被调用, 此时 Vue 会去记录此 Vue ***ponent 所依赖的所有 data。(这一过程被称为依赖收集)
4、data 被改动时(主要是用户操作), 即被写, setter 方法会被调用, 此时 Vue 会去通知所有依赖于此 data 的组件去调用他们的 render 函数进行更新
9、***puted 与 watch 的区别
1、***puted 擅长处理的场景:一个数据受多个数据影响;watch 擅长处理的场景:一个数据影响多个数据。
2、功能上:***puted 是计算属性,watch 是监听一个值的变化,然后执行对应的回调。
3、是否调用缓存:***puted 支持缓存,只有依赖数据发生改变, 才会重新进行计算; 而 watch 不支持缓存,数据变, 直接会触发相应的操作。
4、是否调用 return:***puted 中的函数必须要用 return 返回,watch 中的函数不是必须要用 return。
5、***puted 不支持异步 ,当 ***puted 内有异步操作时无效,无法监听数据的变化; 而 watch 支持异步。
6、***puted 默认第一次加载的时候就开始监听;watch 默认第一次加载不做监听,如果需要第一次加载做监听,添加 immediate 属性,设置为 true(immediate:true)
10、介绍一下 Vue 的生命周期
每一个 vue 实例从创建到销毁的过程,就是这个 vue 实例的生命周期。在这个过程中,他经历了从开始创建、初始化数据、编译模板、挂载 Dom、渲染→更新→渲染、卸载等一系列过程。
将要创建 ===> 调用 beforeCreate 函数
创建完毕 ===> 调用 created 函数
将要挂载 ===> 调用 beforeMount 函数
挂载完毕 ===> 调用 mounted 函数
将要更新 ===> 调用 beforeUpdate 函数
更新完毕 ===> 调用 uodated 函数
将要销毁 ===> 调用 beforeDestory 函数
销毁完毕 ===> 调用 destroyed 函数
11、vue 生命周期的作用是什么?
Vue 生命周期中有多个事件钩子,让我们在控制整个 Vue 实例过程时更容易形成好的逻辑。
12、第一次页面加载会触发哪几个钩子?
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
13、DOM 渲染在 哪个周期中就已经完成?
DOM 渲染在 mounted 中就已经完成了。
14、简单描述每个周期具体适合哪些场景
beforecreate : 可以在这加个 loading 事件,在加载实例时触发
created : 初始化完成时的事件写在这⾥,如在这结束 loading 事件,异步请求也适宜在这⾥调⽤
mounted : 挂载元素,获取到 DOM 节点
updated : 如果对数据统⼀处理,在这⾥写上相应函数
beforeDestroy : 可以做⼀个确认停⽌事件的确认框
nextTick : 更新数据后⽴即操作 dom
15、组件之间是怎么通信的
组件之间通信主要分为三种:父子传参,子父传参,兄弟传参。
-
父子传参:父组件通过自定义属性的方式传参,通过 props 属性给子组件传参,子组件通过 props 属性去接收参数
-
子父传参:子组件通过自定义事件的方式传参,通过 $emit 去进行传参。
16、Vue.cli 中怎样使用自定义的组件?
-
在 ***ponents 目录新建组件文件
-
在需要用到的页面 import 中导入
-
使用 ***ponent 注册
-
在 template 视图中使用组件标签
17、Vue 如何实现按需加载配合 webpack 设置
-
webpack 中提供了 require.ensure( ) 来实现按需加载。以前引入路由是通过 import 这样的方式引入,改为 const 定义方式进行引入
-
不进行页面按需加载引入方式 import home from …/…/***mon/home.vue
-
进行页面按需加载的引入方式:const home = r =>require.ensure([],() =>require('…/…/***mon/home.vue))
18、scss 是什么?在 Vue.cli 中的安装使用步骤是?有哪几大特性?
css 的预编译语言。
使用步骤:
第一步:先装 css-loader、node-loader、sass-loader 等加载器模块;
第二步:在 build 目录找到 webpack.base.config.js,在 extends 属性中加一个拓展. scss;
第三步:在同一个文件,配置一个 module 属性;
第四步:然后在组件的 style 标签加上 lang 属性 ,例如:lang=”scss”;
特性:
-
可以用变量,例如(
$变量名称=值
); -
可以用混合器;
-
可以嵌套
19、如何让 CSS 只在当前组件中起作用?
将当前组件的<style>
修改为<style scoped>
。
20、聊聊你对 Vue.js 的 template 编译的理解?
简⽽⾔之,就是先转化成 AST 树,再得到的 render 函数返回 VNode(Vue 的虚拟 DOM 节点)
详情步骤:
⾸先,通过 ***pile 编译器把 template 编译成 AST 语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),***pile 是 create***piler 的返回值,create***piler 是⽤以创建编译器的。另外 ***pile 还负责合并 option。
然后,AST 会经过 generate(将 AST 语法树转化成 render funtion 字符串的过程)得到 render 函数,render 的返回值是 VNode,VNode 是 Vue 的虚拟 DOM 节点,⾥⾯有(标签名、⼦节点、⽂本等等)
21、Vue 路由跳转的几种方式
第一种方式:router-link (声明式路由)
第二种方式:router.push(编程式路由)
第三种方式:this.$router.push() (函数里面调用)
第四种方式:this.$router.replace() (用法同上, push)
第五种方式:this.$router.go(n)
22、Vue 的路由实现:hash 模式和 history 模式
hash 模式 (默认)
工作原理: 监听网页的 hash 值变化 —> onhashchange 事件, 获取 location.hash
使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
会给用户好像跳转了网页一样的感觉, 但是实际上没有跳转
主要用在单页面应用 (SPA)
history 模式
工作原理: 主要利用 history.pushState() API 来改变 URL, 而不刷新页面.
其实一共有五种模式可以实现改变 URL, 而不刷新页面.
需要后台配置支持, 如果输入一个并不存在的 url, 需要后端配置做 “兜底配置”, 不是粗暴的返回 404, 而是返回首页
23、Vue 与 Angular 以及 React 的区别?
基本概念
Angular 是一个应用设计框架与开发平台,用于创建高效、复杂、精致的单页面应用。
React 是一个用于构建用户界面的 javascript 库
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
三者比较
相同点
-
都是基于 javascript/typescript 的前端开发库,为前端开发提供高效、复用性高的开发方式
-
都有组件和模板的开发思想
-
各自的组件都有生命周期,不用的组件可以卸载,不占用资源
-
都支持指令,如样式、事件等的指令
不同点
-
创始和发行不同:Angular 是由 googl 提供支持的,初始发行于 2016 年 9 月;React 由 Facebook 维护,初始发行于 2013 年 3 月;Vue 是由前 google 人员创建,初始发行于 2014 年 2 月
-
应用类型不同:Angular 支持开发 native 应用程序、SPA 单页应用程序、混合应用程序和 web 应用程序;React 支持开发 SPA 和移动应用程序;Vue 支持开发高级 SPA,开始支持 native 应用程序
-
模型不同:angular 基于 MVC(模型 - 视图 - 控制器)架构;react 和 vue 是基于 Virtual DOM(虚拟的文档对象模型)
4、数据流流向不同:Angular 使用的是双向数据绑定,React 用的是单数据流的,而 Vue 则支持两者。
-
对微应用和微服务的支持不同:Angular 使用的是 TypeScript,因此它更适合于单页 Web 应用(single page web application,SPA),而非微服务。相反,React 和 Vue 的灵活性更适合微应用和微服务的开发。
-
对原生应用的支持不同:React Native 为 iOS 和 Android 开发原生应用;Angular 的 NativeScript 已被原生应用所采用,特别是 Ionic 框架已经被广泛地运用在制作混合应用等方面;Vue 的 Weex 平台正在开发之中,尚无下一步使之成为全面跨平台的计划。
-
框架和库:Angular 是一个框架而不是一个库,因为它提供了关于如何构建应用程序的强有力的约束,并且还提供了更多开箱即用的功能。React 和 Vue 是是一种库,可以和各种包搭配。
-
组件之间传值方式不同:Angular 中直接的父子组件,父组件可以直接访问子组件的 public 属性和方法,也可以借助于 @Input 和 @Output 进行通讯。没有直接关系的,借助于 Service 单例进行通讯;React 组件之间通过通过 prop 或者 state 来通信,不同组件之间还有 Rex 状态管理功能;Vue 组件之间通信通过 props ,以及 Vuex 状态管理来传值
24、vue-router 中导航守卫有哪些?
全局前置守卫、路由独享守卫、组件内守卫
25、 为什么使用 Vue?
-
优点:轻量级的框架、双向数据绑定、组件化开发、单页面路由、学习成本低、虚拟 dom、渐进式框架、数据和结构的分离、运行速度快、插件化
-
缺点:不支持 ie8 以下、社区没有 angular 和 react 丰富、缺乏高阶教程和文档、单页面应用不利用 seo 优化、首次加载时耗时多
26、说出至少 4 种 vue 指令和它的用法?
-
v-if:判断是否隐藏;
-
v-for:数据循环;
-
v-bind:class:绑定一个属性;
-
v-model:实现双向绑定
27、v-if 和 v-show 的区别
-
v-if 通过控制 dom 节点的方式,添加和删除元素,进而实现显示或隐藏元素,v-show 通过设置 dom 元素的 display 来实现显示或隐藏的操作,并不会删除 dom
-
v-if 隐藏会将组件销毁,显示时会将其内部的监听事件重建,v-show 只是设置 display,并不会阻止子组件内部的监听事件
-
v-if 有着更高的切换消耗,v-show 有着更高的初始渲染消耗
28、为什么避免 v-if 和 v-for 一起用?
当 Vue
处理指令时,v-for
比 v-if
具有更高的优先级,通过v-if
移动到容器元素,不会再重复遍历列表中的每个值。取而代之的是,只检查它一次,且不会在 v-if
为否的时候运算 v-for
。
29、什么是 MVVM ?
-
在
MVVM
架构下,View
和Model
之间并没有直接的联系,而是通过ViewModel
进行交互,Model
和ViewModel
之间的交互是双向的, 因此View
数据的变化会同步到Model
中,而Model
数据的变化也会立即反应到View
上。 -
MVVM 是 Model-View-ViewModel 的缩写。MVVM 是一种设计思想。Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。
-
ViewModel
通过双向数据绑定把View
层和Model
层连接起来,而View
和Model
之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM
, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由MVVM
来统一管理。
30、vuex
是什么?怎么使用?哪种功能场景使用它?
vue
中状态管理。在main.js
引入store
注入。新建一个目录store
。场景有:单页应用中,组件之间的状态,音乐播放、登录状态、加入购物车等。
31、vuex
有哪几种属性?
有五种,分别是 State
、 Getter
、Mutation
、Action
、 Module
。
32、vuex
的State
特性
-
Vuex
就是一个仓库,仓库里面放了很多对象。其中state
就是数据源存放地,对应于一般Vue
对象里面的data
。 -
state
里面存放的数据是响应式的,Vue
组件从store
中读取数据,若是store
中的数据发生改变,依赖这个数据的组件也会发生更新。 -
通过
mapState
和 mapGetters 把全局state
和getters
映射到当前组件的***puted
计算属性中。
33、vuex
的Getter
特性
-
getters
可以对State
进行计算操作,它就是Store
的计算属性。 -
虽然在组件内也可以做计算属性,但是
getters
可以在多组件之间复用。 -
如果一个状态只在一个组件内使用,可以不用
getters
。
34、vuex
的Mutation
特性
Action
类似于 mutation
,不同在于:Action
提交的是 mutation
,而不是直接变更状态;Action
可以包含任意异步操作。
35、不用Vuex
会带来什么问题?
-
可维护性会下降,想修改数据要维护三个地方;
-
可读性会下降,因为一个组件里的数据,根本就看不出来是从哪来的;
-
增加耦合,大量的上传派发,会让耦合性大大增加,
Vue
用***ponent
本意就是为了减少耦合,现在这么用,和组件化的初衷相背。
36、keep-alive 的作用是什么?
包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
37、vue-loader 是什么?用途有哪些?
解析.vue
文件的一个加载器。
用途:js 可以写es6
、style
样式可以scss
或less
、template
可以加jade
等。
38、如何获取 dom
在我们的 vue 项⽬中,难免会因为引⽤第三⽅库⽽需要操作 DOM 标签,vue 为我们提供了 ref 属性。ref 被⽤来给元素或⼦组件注册引⽤信息。引⽤信息将会注册在⽗组件的 $refs 对象上。如果在普通的 DOM 元素上使⽤,引⽤指向的就是 DOM 元素;如果⽤在⼦组件上,引⽤就指向组件实例
39、iframe 的优缺点?
iframe 也称作嵌⼊式框架,嵌⼊式框架和框架⽹页类似,它可以把⼀个⽹页的框架和内容嵌⼊在现有的⽹页中。
优点:
-
解决加载缓慢的第三⽅内容如图标和⼴告等的加载问题
-
Security sandbox
-
并⾏加载脚本
-
⽅便制作导航栏
缺点:
-
iframe 会阻塞主页⾯的 Onload 事件
-
即使内容为空,加载也需要时间
-
没有语意
40、请说出 vue.cli 项⽬中 src ⽬录每个⽂件夹和⽂件的⽤法?
assets ⽂件夹是放静态资源;***ponents 是放组件;router 是定义路由相关的配置; view 视图;app.vue 是⼀个应⽤主组件;main.js 是⼊⼝⽂件
41、vue 常⽤的修饰符
-
.stop: 等统⼀ JavaScript 中的 event.stopPropagation(), 防⽌事件冒泡
-
.prevent: 等同于 JavaScript 中的 event。preventDefault(), 防⽌执⾏预设的⾏为 (如果事件可取消,则取消该事件,⽽不停⽌事件的进⼀步
-
capture: 与事件冒泡的⽅向相反,事件捕获由外到内
-
self: 只会触发⾃⼰范围内的事件,不包含⼦元素;
-
once: 只会触发⼀次。
42、⾃定义指令(v-check、v-focus)的⽅法有哪些?它有哪些钩⼦函数?还有哪些钩⼦函数参数?
-
全局定义指令:在 vue 对象的 directive ⽅法⾥⾯有两个参数,⼀个是指令名称,另外⼀个是函数。组件内定义指令:directives
-
钩⼦函数:bind(绑定事件触发)、inserted(节点插⼊的时候触发)、update(组件内相关更新)
-
钩⼦函数参数:el、binding
43、vue 的两个核⼼点
数据驱动,组件系统
-
数据驱动:ViewModel,保证数据和视图的⼀致性
-
组件系统:应⽤类 UI 可以看做全部是由组件树构成的
44、delete 和 Vue.delete 删除数组的区别
-
delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
-
Vue.delete 直接删除了数组 改变了数组的键值
45、Vue-router 跳转和 location.href 有什么区别
使⽤ location.href=/url 来跳转,简单⽅便,但是刷新了页⾯;使⽤ history.pushState(/url), ⽆刷新页⾯,静态跳转;引进 router,然后使⽤ router.push(/url) 来跳转,使⽤了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使⽤ router 跳转和使⽤ history.pushState() 没什么差别,因为 vue-router 就是⽤了 history.pushState(), 尤其是在 history 模式下。
46、RouterLink 在 IE 和 Firefox 中不起作⽤(路由不跳转)的问题
⽅法⼀
只⽤ a 标签,不使⽤ button 标签
⽅法⼆
使⽤ button 标签和 Router.navigate ⽅法
47、请说下封装 vue 组件的过程?
⾸先,组件可以提升整个项⽬的开发效率。能够把页⾯抽象成多个相对独⽴的模块,解决了我们传统项⽬开发:效率低、难维护、复⽤性等问题。
然后,使⽤ Vue.extend ⽅法创建⼀个组件,然后使⽤ Vue.***ponent ⽅法注册组件。⼦组件需要数据,可以在 props 中接受定义。⽽⼦组件修改好数据后,想把数据传递给⽗组件。可以采⽤ emit ⽅法。
48、params 和 query 的区别
⽤法:query 要⽤ path 来引⼊,params 要⽤ name 来引⼊,接收参数都是类似的,分别是 this. r o u t e r . q u e r y . n a m e 和 t h i s . router.query.name 和 this. router.query.name和this.router.params.name。url 地址显⽰:query 更加类似于我们 ajax 中 get 传参,params 则类似于 post,说的再简单⼀点,前者在浏览器地址栏中显⽰参数,后者则不显⽰
注意点:query 刷新不会丢失 query ⾥⾯的数据 params 刷新会丢失 params ⾥⾯的数据
49、vue mock 数据
在项⽬中尝试了 mockjs,mock 数据,实现前后端分离开发。关于 mockjs,官⽹描述的是
-
- 前后端分离
-
- 不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。
-
- 数据类型丰富
-
- 通过随机数据,模拟各种场景。
总结:在后端接⼝没有开发完成之前,前端可以⽤已有的接⼝⽂档,在真实的请求上拦截 ajax,并根据 mockjs 的 mock 数据的规则,模拟真实接⼝返回的数据,并将随机的模拟数据返回参与相应的数据交互处理,这样真正实现了前后台的分离开发。
与以往的⾃⼰模拟的假数据不同,mockjs 可以带给我们的是:在后台接⼝未开发完成之前模拟数据,并返回,完成前台的交互;在后台数据完成之后,你所做的只是去掉 mockjs:停⽌拦截真实的 ajax,仅此⽽已。
50、vue 初始化页⾯闪动问题
使⽤ vue 开发时,在 vue 初始化之前,由于 div 是不会 vue 管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于 {{message}} 的字样,虽然⼀般情况下这个时间很短暂,但是我们还是有必要解决这个问题的。
⾸先:在 css ⾥加上 [v-cloak]{display:none;},如果没有彻底解决问题,则在根元素加上 style=“display:none;” :style=“{display:block}”
51、vue 更新数组时触发视图更新的⽅法
push();pop();shift();unshift();splice();sort();reverse()
52、vue 常⽤的 UI 组件库
Mint UI,element,VUX
53、mint-ui 是什么?怎么使⽤?说出⾄少三个组件使⽤⽅法?
基于 vue 的前端组件库。npm 安装,然后 import 样式和 js,vue.use(mintUi)全局引⼊。在单个组件局部引⼊:import {Toast} from ‘mint-ui’。
-
组件⼀:Toast(‘登录成功’);
-
组件⼆:mint-header;
-
组件三:mint-swiper
54、Vue ⾥⾯ router-link 在电脑上有⽤,在安卓上没反应怎么解决?
Vue 路由在 Android 机上有问题,babel 问题,安装 babel polypill 插件解决
55、Vue2 中注册在 router-link 上事件⽆效解决⽅法
使⽤ @click.native。原因:router-link 会阻⽌ click 事件,.native 指直接监听⼀个原⽣事件。
56、简述 MVVM 框架
Model: 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑。
View: 代表 UI 组件,它负责将数据模型转化成 UI 展现出来。
ViewModel: 监听数据模型的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接 Model 和 View。
57、vue2 和 vue3 区别
-
写法上的区别: vue2 使用的是
options(选项)Api
,vue3 的是***position Api
(当然 vue3 也兼容***position api
)。options Api
中methods,***pute,data
等 api 都是分散的。而***position api
中的代码是根据逻辑功能来组织的, 我们可以将一个功能所定义的methods,***pute,data
等 api 会放在一起, 让我们可以更灵活地组合组件逻辑。 -
vue2 将响应式数据放到 data 函数中, 而 vue3 则是使用
ref
和reactive
将数据声明为响应式 -
响应式实现方式: vue2 中是通过
Object.defineProperty
对数据劫持实现的, vue3 中则是使用Proxy
对数据代理实现的。 -
生命周期区别: vue3 中将
beforeCreate
和created
合并到了setup
函数中 -
根节点: vue3 组件允许多个根节点, 而 vue2 只允许一个
-
内置组件: vue3 新增了传送组件
Teleport
和异步依赖处理组件Suspense
58、v-if 和 v-show
-
v-if
表示一个 dom 元素是否被创建,而v-show
则是控制这个 dom 元素的display
属性是否为none
-
一般在频繁切换状态的地方使用
v-show
,v-if
则更适合条件不经常改变的场景,因为它切换开销相对较大
59、v-for 和 v-if 优先级
-
开发过程中一般不建议同时将 v-for 和 v-if 放在一个标签中使用
-
Vue2 中 v-for 的优先级会更高,所以会先执行循环,再进行 v-if 判断,所以这样就会导致无论需不需展示这个元素,都会先遍历整个列表
-
在 Vue3 中 v-if 的优先级会更高,但是当我们遍历一个数组的时候,根据数组中的某个元素进行 v-if 判断的时候就会报错,因为 v-if 会先执行此时还没有拿到这个数组。所以 Vue3 中会报错
60、介绍 Vue 插槽用法
插槽slot
可以理解为占坑,当使用一个组件的时候,在组件标签里的对应的内容就会替换掉这个组件中的slot
标签。
插槽分为默认插槽
,具名插槽
,作用域插槽
。
默认插槽子组件中用slot
标签来确定渲染位置,父组件使用它时直接在子组件的标签内写入内容即可
//子组件
<template>
<slot />
</template>
//父组件
<Child>
<div>默认插槽</div>
</Child>
具名插槽
顾名思义就是具有名字的插槽,子组件中可以用name
熟悉对slot
命名,父组件在使用的时候通过template
中的v-slot:name
或者#name
来定义这个插槽中的内容
//子组件
<template>
<slot ></slot>
</template>
//父组件
<Child>
<template v-slot:content>具名插槽内容</template>
</Child>
作用域插槽
子组件中的slot
可以通过类似组件属性传递的方式将子组件的值传递给父组件中这个子组件的插槽内容中
(子组件标签内),在父组件使用子组件的时候要用v-slot
的值进行接收这些参数,默认插槽可以将其直接写在子组件标签上,具名插槽则写在template
上。而传过来的值只能在子组件标签中或者template
标签中使用。所以在父组件作用域中获取到了子组件作用域中的变量,可以认为作用域插槽延伸了子组件数据的作用范围,因此叫做作用域插槽
61、过滤器的作用,如何实现一个过滤器
过滤器是用来过滤数据的,在 Vue 中使用 filters 来过滤数据,filters 不会修改数据,而是过滤数据,改变用户看到的输出
使用场景:
-
需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。
-
比如后端返回一个 年月日的日期字符串,前端需要展示为 多少天前 的数据格式,此时就可以用 fliters 过滤器来处理数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在插值表达式 {{}} 和 v-bind 表达式 中,然后放在操作符 “ | ” 后面进行指示
<li>商品价格:{{item.price | filterPrice}}</li>
filters: { filterPrice (price) { return price ? ('¥' + price) : '--' } }
注意 vue3 已经移除了过滤器
62、常见的事件修饰符及其作用
-
.stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;
-
.prevent :等同于 JavaScript 中的 event.preventDefault() ,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
-
.capture :将事件改成捕获模式, 由外到内触发
-
.self :只会触发自己范围内的事件,不包含子元素;
-
.once :只会触发一次。
63、v-model 如何实现的
v-model 其实是一个语法糖, 比如
<input v-model="message" />
等同于
<input
:value="message"
@input="message=$event.target.value"
>
63、Vue2 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?
Vue2 中对象添加新属性绑定的视图不会更新, 因为 Vue2 中 Object.defineProperty 劫持不到 data 对象中新增的属性, 可以使用this.$set(this.obj, 'b', 'obj.b')
解决
$set() 方法相当于手动的去把 obj.b 处理成一个响应式的属性,此时视图也会跟着改变了。
64、Vue 插件用法
面试一般会问你如何写一个 vue 插件, 所以没写过 vue 插件的最好去亲自体验一下
回答:
vue
实例会有一个use
函数, 它接受的是一个带有install
函数的对象和一个可选的选项对象, 当我们使用 vue.use(plugin)
或者app.use(plugin)
会调用我们插件的install
属性的函数, 并且将当前组件的实例传进来. 所以在插件中就可以对这个实例进行一些操作来实现我们插件的功能
65、Vue 自定义指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。面试经常会问什么是自定义指令? 你用自定义指令做过哪些功能?
回答 1: 什么是自定义指令?
-
自定义指令包含局部指令和全局指令, 在模板中使用指令前必须先使用
directives
选项注册。局部指令指在某个组件中注册, 而全局则是将指令注册到全局, 通常在 main.js 中注册。 -
自定义指令由一个包含类似组件生命周期钩子的对象来定义。它的生命周期钩子包含
created
,beforeMount
,mounted
,beforeUpdate
,updated
,beforeUnmount
,unmounted
, -
常用的钩子为
mounted
和updated
, 它接受el
,binding
等参数.binding
参数的值一般包含绑定到这个元素上的信息, 比如下面这个指令
<div v-example:foo.bar="baz">
它的 binding 会是这个对象
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
回答 2: 你用自定义指令做过哪些功能?
-
数据埋点, 通过绑定自定义事件传入点击当前元素需要埋点的事件名, 在指令中监听当前元素的点击事件后调用后台接口将事件名传入
-
权限控制, 通过绑定自定义事件传入控制当前元素的权限字段, 在指令中获取到当前元素根据权限字段来控制该元素的状态 (显示, 隐藏等)
66、***puted 和 watch
-
***puted 是计算属性, 依赖其它属性值, 用于解决模板中放入过多的逻辑会让模板过重且难以维护的问题.watch 是侦听器, 当我们需要根据一个属性的变化而做出一些处理的时候, 可以使用 watch 来对这个属性进行监听
-
***puted 具有缓存的特点, 即当它所依赖的属性发生改变的时候它才会重新执行内部逻辑. 如下代码
<template>
<div>{{ addSum }}</div>
<div>{{ addSum }}</div>
<div>{{ addSum }}</div>
</template>
<script setup>
import { ***puted, ref, watch } from "vue";
const a = ref(1)
const b = ref(2)
let addSum = ***puted(() => {
console.log('内部逻辑执行')
return a.value + b.value
})
</script>
页面多次使用addSum
, 但是只会打印一次 “内部逻辑执行”
-
watch 在页面首次加载的时候默认不会执行, 需要设置
immediate:true
首次才会执行监听 -
watch 默认只监听一层数据, 不监听多层数据里属性的变化, 需要设置
deep:true
才会进行深度监听
67、vue 生命周期
Vue2(选项式 API) | Vue3(setup) | 描述 |
---|---|---|
beforeCreate | - | 实例创建前 |
created | - | 实例创建后 |
beforeMount | onBeforeMount | DOM 挂载前调用 |
mounted | onMounted | DOM 挂载完成调用 |
beforeUpdate | onBeforeUpdate | 数据更新之前被调用 |
updated | onUpdated | 数据更新之后被调用 |
beforeDestroy | onBeforeUnmount | 组件销毁前调用 |
destroyed | onUnmounted | 组件销毁完成调用 |
68、vue 父子组件生命周期执行顺序
这个相对于上一个问题稍微复杂一点, 可以试着理解记忆或者直接记住吧
渲染过程
更新过程
销毁过程
注意如果子组件是异步组件的话它们的执行顺序会发生改变,会先执行完父组件的生命周期然后再执行子组件的生命周期
69、vue 导航 (路由守卫)
路由守卫分为全局路由守卫,路由独享守卫,组件路由守卫
- 全局路由守卫
-
beforeEach
, 接收to、from、next
三个参数,每个路由跳转前都会触发,登录验证时用的比较多 -
beforeResolve
,和beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用 -
afterEach,在路由跳转完成后调用,接收 to、from 两个参数
- 路由独享守卫
beforeEnter
, 一般配置在路由配置文件中(router/index.js),对进入某个路由之前进行相关操作
- 组件路由守卫
接收
to、from、next
三个参数
-
beforeRouteEnter
, 进入该组件之前调用,无法获取到 vue 实例 -
beforeRouteUpdate
,在当前路由改变,但是该组件被复用时调用 -
beforeRouteLeave
, 在离开当前组件时调用
70、Vue-Router 的懒加载如何实现
使用箭头函数 + import 动态加载
const router = new VueRouter({
routes: [{ path: "/list", ***ponent: () => import("@/***ponents/list.vue") }],
});
71、路由的 hash 和 history 模式的区别
Vue-Router 有两种模式:hash 模式和 history 模式。默认的路由模式是 hash 模式。
- hash 模式
简介: hash 模式是开发中默认的模式,它的 URL 带着一个 #,例如:www.abc.***/#/vue,它的 hash 值就是 #/vue。
特点:hash 值会出现在 URL 里面,但是不会出现在 HTTP 请求中,对后端完全没有影响。所以改变 hash 值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的 IE 浏览器也支持这种模式。hash 路由被称为是前端路由,已经成为 SPA(单页面应用)的标配。
原理: hash 模式的主要原理就是 onhashchange() 事件:
window.onhashchange = function (event) {
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
};
- history 模式
简介: history 模式的 URL 中没有 #,它使用的是传统的路由分发模式,即用户在输入一个 URL 时,服务器会接收这个请求,并解析这个 URL,然后做出相应的逻辑处理。 特点: 当使用 history 模式时,URL 就像这样:abc.***/user/id。相比 hash 模式更加好看。但是,history 模式需要后台配置支持。如果后台没有正确配置,访问时会返回 404。 API: history api 可以分为两大部分,切换历史状态和修改历史状态:
修改历史状态:包括了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了 url,但浏览器不会立即向后端发送请求。如果要做到改变 url 但又不刷新页面的效果,就需要前端用上这两个 API。
切换历史状态: 包括 forward()、back()、go() 三个方法,对应浏览器的前进,后退,跳转操作。
虽然 history 模式丢弃了丑陋的 #。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出 404 来。
如果想要切换到 history 模式,需要后端进行一些配置:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的根页面
Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
nginx
location / {
try_files $uri $uri/ /index.html;
}
72、nexttick 原理
关于 nextTick 会问到它的用法, 然后是它的原理, 然后还可能问到 JS 的时间循环机制。
问题 1:vue 中的 nextTick 是干什么用的?
这个其实比较简单, 用过都知道它是干嘛的, vue 官方的解释是:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
这是什么意思呢, 其实 vue 中修改 data 不会立刻触发 dom 更新; 而是把需要更新的 Watcher 加入到 queueWatcher 队列中, 然后在合适的时机在 nextTick 中调用这些 Watcher 的更新函数进行 dom 更新, 所以在 data 刚被修改的时候, 我们是获取不到更新后的 dom 的, 这时候便需要调用 nextTick 函数在它的回调函数中获取到变化后的 dom
问题 2:nextTick 原理
-
nextTick 原理是借助浏览器事件循环来完成的, 因为每次事件循环之间都有一次视图渲染, nextTick 尽量在视图渲染之前完成 dom 更新, 所以 nextTick 优先使用的是 promise(微任务) 实现
-
每次执行 nextTick 时会将传入的回调函数放入一个队列中 (callbacks 数组), 然后当在本次事件循环的同步代码执行完毕后开启一个微任务(promise 或者 MutationObserver) 去依次执行这个 callbacks 中的回调函数。
-
但是当浏览器不支持 promise 的时候在 vue2 中会进行进行降级处理, 依次使用
setImmediate
、setTimeout
开启一个宏任务执行 callbacks -
当一个 data 数据更新时对应的 watcher 便会调用一次 nextTick, 将它对应的 dom 更新操作作为回调函数放入 callbacks 中, 所以当我们想获取这个 data 更新后的 dom 需要在其值变化后也调用 nextTick 将回调函数传入排在上个更新 dom 的回调函数后面, 所以我们可以在这个 nextTick 的回调函数中获取到更新后的 data
73、Vue 组件传参
这里我大概归纳了一下 vue2 和 vue3 的传参方式
方式 | Vue2 | Vue3 |
---|---|---|
父传子 | props | props |
子传父 | $emit | emits |
父传子 | $attrs | attrs |
子传父 | $listeners | 无 (合并到 attrs 方式) |
父传子 | provide/inject | provide/inject |
子组件访问父组件 | $parent | 无 |
父组件访问子组件 | $children | 无 |
父组件访问子组件 | $ref | expose&ref |
兄弟组件传值 | EventBus | mitt |
74、Vuex
-
Vuex 是 Vue 中的全局状态管理框架,它可以管理应用的所有组件的状态。并不是每个项目都需要引入 Vuex 的,当我们的项目有很多个页面,并且这些页面共享着多个数据状态,此时我们可以引入 Vuex。
-
Vuex 有三个核心的概念,
state
,mutations
,actions
, 其中state
为存放数据的地方,mutations
中的函数作用则是用来修改state
,actions
中一般是用了处理一些异步操作的函数。 -
Vuex 除了上面三个概念还有
getters
,moudles
,getters
就像 Vue 中的计算属性***puted
一样用来描述依赖响应式状态 state 中的复杂逻辑。moudles
则是可以将 store 分割成模块(module),每个模块都拥有自己的state
,mutations
,actions
等,在大型应用中经常用到 -
场景:当我们异步获取结果并赋值给 state 的时候,比如数据请求,我们可以在
actions
中进行数据请求,拿到结果通过它的dispatch
方法调用mutations
中修改state
的函数,从而将结果赋值给了state
75、Pinia
pinia
其实就是 Vuex5,它和 Vuex 的主要区别有以下几点
-
Pinia 使用更简单,更符合开发者的开发习惯
-
pinia
中没有了mutations
, 状态state
的修改可以直接进行修改,或者在actions
中修改,或者使用它的$patch
方法进行修改 -
pinia
中没有了modules
, 如果想使用多个 store,直接使用defineStore
定义多个 store 传入不同的 id 即可 -
更好的 TS 支持,不需要创建自定义的复杂包装器来支持 TS
76、vue2 混入 - Mixin
vue3 中已经没有Mixin
这个概念了,所以未来被问到的几率会越来越小,但是目前被问到的频率还是很高的。一般会它的概念以及优缺点,有时还会问它与父组件的生命周期执行顺序
vue 官网描述:
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被 “混合” 进入该组件本身的选项。
回答 1.Mixin
的作用将组件的公共逻辑提取出来,哪个组件需要用到时,直接将提取的这部分混入到组件内部即可 2. Mixin
的生命周期会在父组件生命周期之前执行,如果Mixin
中的属性或者方法与父组件冲突则会使用父组件中的 2. 优点:可以降低代码冗余提高逻辑复用性。 3. 缺点:命名容易冲突,不好追溯源,后期排查不方便
77、对虚拟 dom 的理解
简单来说就是一个描述 dom 结构的 js 对象
-
每当我们用原生 JS 或者 JQ 操作
DOM
时,浏览器会从头开始进行DOM
树的构建,频繁的操作DOM
开销是很大的。 -
而虚拟
DOM
就是为了减少这些操作的,虚拟DOM
首先会通过状态生成一个虚拟节点树(js 对象),然后使用虚拟节点树进行渲染。当某些状态发生变更时会生成新的虚拟 DOM 节点树,然后与上一次虚拟 DOM 节点树进行比较(diff),从而找到差异的部分,最后渲染到真实的 DOM 节点上面
78、keep-alive
官网描述
<KeepAlive>
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
回答
-
通常我们切换组件的时候,上一个组件就会被销毁,而当我们使用
<KeepAlive>
将其包裹的话这个组件就会被缓存,当这个组件再一次被显示时就会保留之前的状态。 -
keep-alive
接收两个属性include
和exclude
,分别代表哪些组件要用缓存和哪些不需要缓存,它接收组件的名字数组,字符串或者正则,当我们使用动态组件***ponent
或者路由router-view
的时候可以使用 -
keep-alive
还接收max
属性表示最大缓存实例数,如果超出这个数则最久没有被访问的缓存实例将被销毁。 -
keep-alive
有两个生命周期,分别是activated
和deactivated
,activated
钩子会在首次挂载或者每次从缓存中被重新插入的时候调用。deactivated
钩子则是在组件从 DOM 上移除或者组件卸载时调用
79、vue3 新增的内置组件
-
Teleport
组件
可以称之为传送门,作用将其插槽内容渲染到 DOM 中的另一个位置,接收两个参数 to(要去的位置)和 disabled(是否留在原位置)。接收比如下面代码
<teleport to="#popup">
<video src="./my-movie.mp4">
</teleport>
video 将会被传送到 id 为 popup 的元素下。
-
Suspense
组件
-
<Suspense>
组件用于协调对组件树中嵌套的异步依赖的处理。 -
它一般用于包裹多个异步组件处理多个异步组件加载前与完成后的统一状态
-
<Suspense>
组件有两个插槽:#default
和#fallback
, 在初始渲染时,<Suspense>
将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态等待异步组件加载完毕。在挂起状态期间,展示的是#fallback
插槽内容
80、对 SSR 的理解
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端
SSR 的优势:
-
更好的 SEO
-
首屏加载速度更快
SSR 的缺点:
开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子; 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境; 更多的服务端负载。
81、vue 响应式原理
-
vue 的响应式原理是根据
Object.defineProperty
这个 api 来对数据进行劫持并结合发布者 - 订阅者模式实现的 -
首先会利用
Object.defineProperty
中的get
函数来对 vue 中的 data 的所有属性进行访问劫持, 中间会涉及到劫持 data 中更深层次的属性需要递归调用劫持方法。这里是通过一个Observer
类实现的 -
劫持到每一个属性后会给这个属性绑定多个订阅者
watcher
, 因为一个属性可能被用在很多地方; 而这个watcher
中则包含更新视图的函数update
。 -
watcher
和属性的对应关系以及和视图的联系则是通过编译模板***pile
类来实现的。***pile
中会拿到整个 dom 对象, 然后遍历元素子节点获取到使用过 vue 中 data 属性的则给该属性直接添加一个watcher
并赋予一些更新当前视图的方法. -
每个属性的多个订阅者
watcher
都会被添加到对应的数组中, 这里则是通过Deps
类实现的, 初始化watcher
的时候会调用Deps
中的addSub
方法将对应watcher
添加该类的Subs
数组中 -
当 data 中的某个属性发生改变时则会触发
Object.defineProperty
中的set
函数, 这时便会调用该属性的Deps
类中的notify
函数遍历Subs
数组中的订阅者watcher
并调用其函数update
去触发视图的更新
82、Object.defineProperty 和 proxy 区别
-
Object.defineProperty
只能代理属性,Proxy
代理的是对象。 -
对象上新增属性,
Proxy
可以监听到,Object.defineProperty
不能。 -
Object.defineProperty
的代理行为是在破坏原对象的基础上实现的,Proxy 则不会破坏原对象,只是在原对象上覆盖了一层。 -
数组新增修改,
Proxy
可以监听到,Object.defineProperty
不能。 -
Proxy
不兼容IE11
及以下