[toc]
# 题1、vm.$nextTick 是干什么用的?为什么用?
答:
效果:通过 `this.$nextTick` 我们可以注册一个回调函数,这个回调函数会在下次 DOM 更新结束之后执行
为什么用?
答:Vue 中的数据更新是 `延迟异步更新` 的?就是说当我们修改了数据之后,页面并不会马上就更新,所以如果我们修改了数据之后,马上就获取页面中的数据,你会发现他还是原来的数据因为页面还没有更新,比如:
~~~
this.name = 'tom' // 更新数据,此时数据虽然修改了,但是页面并没有更新
// 获取页面中的 dom 元素的值,此时获取到的并不是新修改的 tom ,而是修改之前的原值
// 因为此时上面的 tom 还没有更新到页面中(延迟异步更新)!!!!!
let domValue = document.getElementById('#name').innerHTML
// 所以如果要获取修改之后的值,可以在新修改的数据更新到页面中之后再获取:
this.$nextTick(()=>{
// 这里获取的就是更新之后的新值
let domValue = document.getElementById('#name').innerHTML
})
~~~
使用场景之一、
你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是什么呢,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。
# 题2、生命周期函数有哪些?项目中是怎么使用的?
答:
* beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。此时组件的选项对象还未创建,el 和 data 并未初始化,因此无法访问 methods, data, computed 等上的方法和数据。
* created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,`$el` property 目前尚不可用。
* beforeMount:挂在开始之前被调用,相关的 render 函数首次被调用(虚拟 DOM),实例已完成以下的配置:编译模板,把 data 里面的数据和模板生成 html,完成了 el 和 data 初始化,注意此时还没有挂在 html 到页面上。
* mounted:挂载完成(HTML已经被渲染到了页面上)。这时可以安全的执行一些 DOM 操作。注意 `mounted` **不会**保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 `mounted` 内部使用 `this.$nextTick`。
* beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。可以在该钩子中进一步地更改状态,不会触发附加地重渲染过程
* updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。注意 `updated` **不会**保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 `updated` 里使用 vm.$nextTick。
* beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
* destroyed:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
* errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 `false` 以阻止该错误继续向上传播。
* activated:被 keep-alive 缓存的组件激活时调用。
* deactivated:被 keep-alive 缓存的组件停用时调用。
项目中的应用场景:
beforeCreate: 可以在此时加一些 loading 效果,在 created 时进行移除
created: 需要异步请求数据的方法可以在此时执行,完成数据的初始化
mounted: 当需要操作 dom 的时候执行,可以配合$.nextTick 使用进行单一事件对数据的更新后更新dom
updated: 当数据更新需要做统一业务处理的时候使用
# 题3、在 Vue 中是如何绑定事件?事件修饰符是干什么用的?说出10个常用的修饰符?
答:
1. 在 Vue 中使用 `v-on` 指令来绑定一个事件,事件可以是 methods 中定义的方法,也可以直接调用一个函数,比如绑定点击事件:`v-on:click="methods中的方法"`,也可以使用 `@` 来进行简写。
2. 事件修饰符可以在绑定事件时添加额外的功能,比如:阻止事件冒泡等
3. 修饰符使用的方法是在绑定的事件名称后面添加 `.修改符名称`,如:`v-on:click.stop="xxx"`
4. 常用的修饰符有:
.prevent:阻止默认行为
.stop:阻止阻止冒泡
.once:事件只触发一次
.capture:在捕获阶段触发
.self:只当在 event.target 是当前元素自身时触发处理函数
.native:在自定义组件上可以绑定原生事件,事件会被绑定到根元素上
.lazy:输入框(input、textarea)默认在 input 事件时同步使用 v-model 绑定的数据,这样事件触发的非常频繁,所以可以使用这个修饰符,改为在 `change` 事件时同步数据
.trim:如果要自动过滤用户输入的首尾空白字符,可以给 `v-model` 添加 `trim` 修饰符
.number:如果想自动将用户的输入值转为数值类型,可以给 `v-model` 添加 `number` 修饰符。
.enter:使用键盘事件时捕获点击回车键的事件。
# 题4、Vue2中数据响应式的实现原理是什么?数组的响应式?
答:
Vue2 响应式原理:
1. Vue2 中使用了 `Object.defineProperty` 方法为 data 中定义的每个数据设置 get 和 set 拦截器
2. 在 get 中收集依赖(哪个组件使用了这个数据)
3. 在 set 中触发依赖(让使用了这个数组的组件进行更新)
4. 每个组件都是被包在一个 `Watcher` 类中的,这个类中保存了渲染这个组件的函数
5. 每个数据都通过一个 `Dep` 类来收集使用了这个数据的组件的 `Watcher` 类
6. 当数据被修改时从这个数据的 Dep 类中取出所有依赖这个数据的 Watcher 类,并调用这个类的渲染方法
7. Watcher 类中的渲染方法会重新进行模板解析得到模板的(AST:抽象语法树),然后再将AST 转成 VDOM(虚拟DOM),然后和原VDOM做DIFF算法生成PATCH(补丁)最后将补丁更新到页面上
Vue2 中数组响应式原理:
Vue2 中重写了 `Array.prototype` 上的七个方法(push、pop、splice、shift、unshift、sort、reverse),所以对于数组来说,只有调用这7个方法中的一个时,数据才是响应式的(页面会自动更新),如果直接通过下标操作数组中的非对象类型的值,页面是不会更新的,这时可以使用 `vm.$set` 方法来实现响应式:
~~~
this.$set(数组数据, 下标, 值)
~~~
# 题5、Vue 组件中的 data 为什么是函数?
答:
1. 跟按引用传值有关。
2. Vue 中的 data 是一个对象类型,对象类型的数据是按 `引用传值` 的。这就会导致所有组件的实例都 `共享` 同一份数据,这是不对的,我们要的是每个组件实例都是独立的
3. 所以为了解决对象类型数据共享的问题,我们需要将 data 定义成一个函数,每个实例需要通过调用函数生成一个独立的数据对象
# 题6、什么是计算属性?有什么特点?如何修改?应用场景?和监听器有什么区别?
答:
1. 在 Vue 中使用 `computed` 来定义计算属性。
2. 每个计算属性就是一个函数,这个函数需要有一个返回值。
3. 在模板中可以像数据一样直接通过 `{{ xx }}`显示计算属性的值,显示的值就是函数的返回值
4. 计算属性的特点是:只有当函数中依赖的数据改变时,计算属性函数才会重新调用。
5. 修改计算属性的值:需要在定义计算属性时将函数改成一个对象并包含 `getter` 和 `setter` 两个函数,并在 setter 中编写修改计算属性的代码。
6. 应用场景:购物车中的总价,等需要得到 `经过处理` 的数据时使用
7. 和监听器的区别:
1. 计算属性主要用途:得到一个`经过计算的数据` 并且当依赖的数据改变时重新计算,重点在于得到数据
2. 监听器的主要用途:当依赖改变时函数一个函数,重点在干一件事,这件事通常比较复杂,比如:当搜索条件改变时,重新调用后端接口等
# 题7、什么是监听器?深度监听?应用场景?和计算属性的区别?
答:
1. 在 Vue 中使用 `watch` 属性来定义一个监听器函数,当依赖的数据发生变化时触发函数
2. watch 的本质就是调用了 Vue 中的 $watch 方法对数据进行监听
3. $watch 方法的返回值一个函数,可以用来取消监听
4. 默认是浅监听,即当监听的对象的子对象发生改变时是不会触发监听函数的,通过使用 `deep` 选项可以设置监听器为深度监听,即当子对象改变时会触发监听函数
5. 应用场景:在后台管理系统中,在制作数据列表时,每当修改了搜索、排序、翻页条件时就要重新根据新的条件调用接口获取数据,这个功能就可以使用监听器监听这些条件,每当发生改变时就重新调用接口获取数据。
6. 与计算属性的区别:
1. 计算属性主要用途:得到一个`经过计算的数据` 并且当依赖的数据改变时重新计算,重点在于得到数据
2. 监听器的主要用途:当依赖改变时函数一个函数,重点在干一件事,这件事通常比较复杂,比如:当搜索条件改变时,重新调用后端接口等
# 题8、什么是过滤器?项目中是怎么使用的?如何使用过滤器?
答:
1. 过滤器主要用来对数据进行格式的修改,在变量后通过 `管道符(|)` 来使用
2. 过滤器分为 `全局过滤器` 和 `局部过滤器`。
全局过滤器:使用 `Vue.filter` 在创建 `new Vue` 之前执行定义全局过滤器,全局过滤器在任何一个组件中可以直接使用。
局部过滤器:只能在注册了的组件中使用。组件中使用时需要先引入过滤器,然后再注册到组件的 `filters` 属性中,然后才能使用。
# 题9、什么是混入?项目中是怎么使用的?
答:在 Vue 组件中通过 `mixins` 属性来引入一个混入的 JS 文件中的代码。
用途:可以把多个组件共用的 JS 代码单独提取出来放到一个 JS 文件中,然后哪个组件中需要就直接混入。
之前写的商城后台的项目:使用了混入实现的把组件中的JS 代码和 HTML+CSS 分离写在两个文件中(都写在一个 .vue 文件中代码太长,所以就把 JS 单独分出来了,然后通过混入合并到 .vue 文件中)。
实现思路:
1. 把 JS 代码单独写到一个 JS 文件中
2. 在 .vue 文件中使用 mixins: \[ js 文件\] 混入进来
# 题10、如何属性值绑定?class 和 style 的属性绑定有什么特点?
答:
1. 组件上的属性值都通过 `v-bind` 来进行绑定,简写为 `:` ,比如:`v-bind:src='xxx'`
2. class 和 style 这两个属性在绑定时值有两种形式:
1. 对象:`{[key: String] : Boolean}`,键是类名,值是布尔,含义是当值为真是添加这个键做为类,比如:`:class="{a: true,b:true,c:false}"` 结果:`class="a b"`
2. 数组:数组中的每一项都会添加上,比如:`:style="[a,b]"` 结果会把 a,b两个变量中保存的样式都应用上
# 题11、在 Vue 中使用一个自定义组件的流程是?
答:
自定义组件有两种情况:
* 全局组件:
* 在创建 Vue(new Vue)实例之前通过 `Vue.component` 定义全局组件
* 在任何一个组件的模板中直接使用,如:
* 局部组件
* 创建一个自定义组件,比如:Hello.vue 组件
* 在组件中使用时需要先使用 import 引入这个自定义组件,比如:import Hello from 'Hello.vue'
* 引入之后需要在 components 属性中注册这个组件,如:`components: { Hello }`
* 注册之后就可以在模板中使用了:
# 题12、组件之间如何传值?
答:组件间传值分为三种:父向子、子向父、兄弟间。
第一种:父传子:主要通过 props 来实现的
具体实现:父组件通过 import 引入子组件,并注册,在子组件标签上添加要传递的属性,子组件通过 props 接收,接收有两种形式一是通过数组形式\[‘要接收的属性’ \],二是通过对象形式{ }来接收,对象形式可以设置要传递的数据类型和默认值,而数组只是简单的接收
第二种:子传父:主要通过$emit 来实现
具体实现: 子组件通过通过绑定事件触发函数, 在其中设置this.要派发的自定义事件,要传递的值,emit 中有两个参数一是要派发的自定义事件,第二个参数是要传递的值
第三种:兄弟之间传值有两种方法:
方法一:通过 event bus 实现
具体实现:创建一个空的 vue 并暴露出去,这个作为公共的 bus,即当作两个组件的桥梁,在两个兄弟组件中分别引入刚才创建的bus,在组件 A 中通过 bus.(自定义事件名,要发送的值)发送数据,在组件中通过on(‘自定义事件名‘,function(v) { //v 即为要接收的值 })接收数据
方法二:通过 vuex 实现
具体实现:vuex 是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括 state,actions,mutations,getters 和 modules 5 个要素,主要流程:组件通过 dispatch 到 actions,actions 是异步操作,再 actions中通过 commit 到 mutations,mutations 再通过逻辑操作改变 state,从而同步到组件,更新其数据状态
# 题13、组件上属性的特点?注意事项?如何修改?
答:
1. 子组件中通过 `props` 来定义属性
2. 定义属性有两种形式:数组和对象
3. 对象形式的属性在定义时可以指定属性值的类型、是否必填、默认值等
4. 属性一般是单向的从父组件传向子组件,一般子组件中不允许修改父组件传过来的属性值
5. 当父组件中的值改变时,子组件中的属性值也会同步更新
6. 在子组件中如果要修改属性值有以下两种方法:
1. 通过 `$emit` 向父组件发送一个事件,然后在父组件中修改
2. 在父组件中绑定属性值时使用 `.sync` 修饰符,这样在子组件中修改属性值时不会报错
# 题14、style 上的 scoped 是什么意思?实现原理是什么?
答:
1. 定义在 scoped 的 style 标签中的样式只对当前组件生效,不影响其它组件和子组件。
2. 实现原理:
1. 当组件中使用了 scoped 的 style 标签之后,Vue 会给当前组件中所有的标签上都添加一个形为 `data-v-xxxx` 形式的属性
2. Vue 会给 CSS 代码中样式的选择器上添加属性选择器以限制只对当前组件生效,如:`a[data-v-xxx] {...}`
3. 需要注意的是:使用了 scoped 之后,组件中所有标签和样式选择器上都会添加一个 `data-v-xxxx` 的属性,页面会因为多了这些字符串页变大,影响页面的加载性能
# 题15、什么是插槽?如何定义插槽?匿名和有名插槽?如何向插槽中传值?
答:
1. 当我们向子组件中传值时,我们可以使用属性,但属性只能传一些数据,如果我们要传的是一段HTML结构或者是一个组件时,属性就做不到了。
2. 一个组件可以在特定的位置上定义插槽,然后父组件就可以向这些位置上传入HTML结构或者是另一个组件,所以`插槽是用来向一个组件中传入另一个组件或者一段HTML结构用的`
定义插槽:在组件中使用 `slot` 组件来定义一个插槽。
插槽分为两种:
匿名插槽:没有设置名称的插槽,一个组件中只能有一个匿名插槽,
有名插槽:通过 name 属性设置了名字的插槽,比如:
父组件向插槽传值时分为两种情况:
匿名插槽:在组件的开始和结构标签之间放的内容都会放到匿名插槽中,如:这里的内容放至匿名插槽中
具名插槽:通过 `v-slot` 指令指定要放到的有名插槽中,如:放到a标签中,简写是 `#`
# 题16、v-if 和 v-show 的区别?
答:
共同:v-if 和 v-show 都是控制一个元素是否显示。
区别:v-if 如果是 false 就不渲染这个元素,页面中没有这个元素
v-show 无论 true 和 false 都会渲染这个元素,页面中始终有这个元素,当 false 时使用 display: none 把它隐藏。
# 题17、Vue 中的 key 是干什么用的?
答:
在做循环时 Vue 要求我们必须要加上 key 属性,并且要为循环的数据设置不一样的 key 值,这样的目的是用来区分和识别一个元素是否有改变的,有了这个 key 时在后续的排序、修改等更新操作时性能更快。
# 题18、template 组件的用途是?
答:当我们需要把多个连续的标签做为一个整体进行操作时,我们一般会使用一个标签把它们套起来然后统操作,比如:有3个a标签,然后我们需要通过 v-if 来判断是否显示这三个a标签,如果在这三个a标签上都写指令的话,需要写3遍,所以我们一般把它套起来统一处理:
~~~
<div v-if="show">
<a href>xxx</a>
<a href>xxx</a>
<a href>xxx</a>
</div>
~~~
但这样写就会在页面中多渲染出一个 div 来,所以这时我们可以使用 `template` 标签来将多个标签套起来统一操作,但最终在渲染时不渲染这个 template 标签:
~~~
<template v-if="show">
<a href>xxx</a>
<a href>xxx</a>
<a href>xxx</a>
</template>
~~~
# 题19、描述在项目中使用 vue router 的流程?子路由?vue router 实现原理?
答:
使用流程:
1. 创建一个对象,通过 path 和 component 属性配置 URL 路径和组件的对应关系。
2. 通过这个配置对象创建路由:new VueRouter({routes})
3. 页面中添加 `router-view` 组件显示匹配到的组件
4. 页面中使用 `router-link` 组件制作路由跳转的按钮
5. 在JS中可以使用 `this.$router.push` 方法实现路由页面的跳转
子路由:
~~~
1. 在路由配置中使用 children 属性来配置子组件
2. 在子页面中再使用 `router-view` 组件设置子页面显示的位置
~~~
# 题20、keep-alive 是干什么用的?怎么用?
答:一个路由在切换时会被销毁,之前的数据全部丢失,下次再访问这个组件时,需要重新创建,重新调接口,重新渲染,为了提高性能我们可以使用 keep-alive 把组件缓存起来,这样在组件切换时,这个组件并没有被销毁,下次访问时,可以就可以显示出来,而且原组件中数据还在。
把需要缓存的组件使用 keep-alive 套起来即可。比如:把所有路由页面都缓存起来,在切换时不销毁:
~~~
<keep-alive>
<router-view />
</keep-alive>
~~~
有两个参数 include - 字符串或正则表达,只有匹配的组件会被缓存
exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
还可以使用 include 和 exclude 来设置哪些缓存,哪些不缓存,比如:不缓存登录页:
~~~
<keep-alive exclude="login">
<router-view />
</keep-alive>
~~~
# 题21、如何实现路由之间的传参数?
答:路由之间的传参数主要有两种方式:
方式一、? 后传参数【query方式】
直接在跳转时的路径后面添加 ? 然后添加要传的参数,如:`/users?id=100&page=1`
然后在下一个页面中使用 `this.$route.query.xx` 来接收。
形式二、路由传参数【params方式】
在配置路由对象时直接把参数部分定义在路由上,如在配置路由时的 path 上写:`/home/user/:id`。
然后在下一个页面中使用 `this.$route.params.xx` 来接收。
# 题22、什么是动态组件?如何使用?
答:可以动态的修改要显示的组件。
Vue 中提供了一个 `component` 组件,这个组件只是一个占位符,它本身不显示任何内容,这个组件上有一个 `is` 属性,这个属性用来设置要显示的组件的名字。通过修改这个 is 属性就可以让 component 显示不同的组件。
# 题23、路由导致守卫有几种?参数的用途?
答:主要分三种:`全局守卫`、`组件内守卫` 和 `路由独享守卫`。
* 全局路由守卫 :每次路由跳转都会被调用。
全局路由守卫分为:`前置`和`后置`
前置:使用 beforeEach 来定义,在每次跳转之前执行
后置:使用 afterEach 来定义,在每次跳转之后执行
* 组件路由守卫:在进入某个 Vue 组件时调用。
在组件中定义路由守卫,有三个:
beforeRouteEnter(to, from ,next):在路由进入组件之前,组件实例还未渲染,所以无法获取 this 实例,只能通过 vm 来访问组件实例
beforeRouteUpdate(to, from, next):同一页面,刷新不同数据时调用
beforeRouteLeave(to, from, next):离开当前路由页面时调用
* 路由独享的守卫
路由独享守卫是在路由配置页面单独给某个路由配置的一个守卫。在配置路由时直接使用
beforeEnter(to, from, next):定义守卫
三个参数的含义:
to:对象,将要跳转到的路由对象。
from:对象,跳转前的路由对象。
next:函数,控制是否跳转,分为同种情况:
next():进入下一个钩子。
next(false):中断当前的导航,阻止跳转。
next(新的跳转):跳转到新的跳转
# 题24、Vue 中的自定义指令?自定义指令的钩子函数?钩子函数的参数?
答:除了 Vue 中自带的 v-if、v-bind、v-for、v-model 等指令之外,我们还可以自定义指令。
自定义的指令分为 `全局指令` 和 `局部指令` 两种。
全局指令:在创建 Vue 实例之前,使用 `Vue.directive` 来创建全局指令,全局指令可以在所有组件中直接使用。
局部指令:在组件中使用 `directives` 属性可以定义局部指令,这个指令只在当前这个组件中可以使用。
在创建自定义指令时,可以定义指令的生命周期函数:
* `bind`:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
* `inserted`:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
* `update`:所在组件的 VNode 更新时调用,**但是可能发生在其子 VNode 更新之前**。
* `componentUpdated`:指令所在组件的 VNode **及其子 VNode** 全部更新后调用。
* `unbind`:只调用一次,指令与元素解绑时调用。
钩子函数中参数:
* `el`:指令所绑定的元素,可以用来直接操作 DOM。
* ~~~
binding
~~~
:一个对象,包含以下 property:
* `name`:指令名,不包括 `v-` 前缀。
* `value`:指令的绑定值,例如:`v-my-directive="1 + 1"` 中,绑定值为 `2`。
* `oldValue`:指令绑定的前一个值,仅在 `update` 和 `componentUpdated` 钩子中可用。无论值是否改变都可用。
* `expression`:字符串形式的指令表达式。例如 `v-my-directive="1 + 1"` 中,表达式为 `"1 + 1"`。
* `arg`:传给指令的参数,可选。例如 `v-my-directive:foo` 中,参数为 `"foo"`。
* `modifiers`:一个包含修饰符的对象。例如:`v-my-directive.foo.bar` 中,修饰符对象为 `{ foo: true, bar: true }`。
* `vnode`:Vue 编译生成的虚拟节点。
* `oldVnode`:上一个虚拟节点,仅在 `update` 和 `componentUpdated` 钩子中可用。
# 题25、使用 axios 时,如何配置基地址?为什么要配置?
答:
有两种方法:
写法一、直接在 axios 上设置:`axios.defaults.baseURL = 'http://xxxxx'`
写法二、在创建时使用 baseURL 属性:
~~~
const request = axios.create({
baseURL: 'http://xxxxx'
})
~~~
为什么要配置?
答:每次调用接口时都需要写上完整的接口地址,但是每个接口前面的地址是相同的,所以我们可以把接口地址中前面相同的部分提取出来设置一下,这样在后面调用接口时就不需要写前面的地址了。
每个接口地址前面相同的部分就是基地址。
# 题26、axios 中有几个拦截器?项目中是怎么使用的?
答:两个拦截器:
前置(请求)拦截器:就是一个函数,在每次调用接口之前都会触发的函数。
后置(响应)拦截器:就是一个函数,每次服务器返回结果之后触发的函数。
基于这两个函数的特点:
前置拦截器(为每次请求添加令牌):
\\1. 请求接口时要把令牌放到协议头上提交给接口
\\2. 每次请求接口之前都放令牌比较麻烦
\\3. 所以,可以在前置拦截器中只写一次代码,就可以在每次请求时把令牌放上
后置拦截器(判断每次请求是否失败):
\\1. 每次调用完接口之后都要判断是否失败,如果失败就提交错误信息
\\2. 我们可以在后置拦截器中写一次代码,判断如果返回错误就提示错误信息
\\3. 这样之后,以后在项目中就不用再判断接口是否失败了
# 题27、Vuex 是干什么用的?为什么要使用?怎么用?由哪几部分组成?分别干什么用的?
答:
1. 用途:实现组件之间数据的共享。
2. 为什么用:在 Vue 中我们可以通过属性和事件实现父子之间的传值,但需要一级一级的传值,如果层级较深,只通过属性和事件传值非常的麻烦,所以需要一个专门的工具来对数据进行管理,以方便的实现各层级组件之间数据的共享。
3. 组成部分:vuex 中主要有 6 部分组成:
state:定义数据
mutations:定义操作数据的方法,简单的操作,不能是异步的
actions:定义操作数据的复杂的方法,比如AJAX等异步代码
getters:先处理state中的数据,然后返回处理之后的结果,有点类似过滤器
modules:分模块使用
plugins:插件
# 题28、什么是虚拟DOM?diff 算法?有什么用?
答:
1. 进行 DOM 时比较耗时的操作,因为浏览器需要重绘,我们应该尽量避免 DOM 操作
2. Vue 在内存了维护一个对象类型的数据,这个数据保存了当前 DOM 的结构,这个对象注叫做虚拟DOM
3. 每次再修改了数据之后,先算出修改数据之后的虚拟 DOM 结构,然后和原虚拟DOM 结构进行对比,找出不同的地方,然后只更新不同的地方进行 DOM 操作,这样就有效的减少了 DOM 操作以提高性能,这种比较新旧虚拟DOM找出不同点的算法就叫做 DIFF 算法
4. DIFF 算法的流程是:只比较同一层级的元素,如果不同,那么子元素就不再比较了直接认为不同,如果相同再比较子元素找出不同的地上,使用的是深度优先遍历的方法
5. 如果每个节点一个个的比较那么算法的复杂度的 n^3 ,这是无法接受的,所以 diff 算法只对同一层级同一位置的元素进行对比,这样的时间复杂度是 n。
# 题29、如何在组件中使用 Vuex 中的数据?有几种使用方式?
答:组件中使用 Vuex 中的数据有两种方式:
方式一、直接通过 `this.$store.state.xxx` 读取
~~~
<!-- 模板中 -->
<div>
{{ $store.state.name }}
</div>
// JS 中
console.log( this.$store.state.name )
console.log( this.$store.state.age )
~~~
方式二、引入并映射到计算属性中使用
~~~
// 1. 引入 mapState
import { mapState } from 'vuex'
// 2. 在 computed 中进行映射
computed: {
// 把 Vuex 中的 name 和 age 映射到当前组件中来
...mapState(['name', 'age'])
}
// 3. 映射完之后,就相当于本组件中的数据,可以直接使用
<div>
{{ name }}
</div>
console.log( this.name )
~~~
# 题30、mutations 和 actions 分别如何使用?有什么区别?
答:
1. 只有 mutations 中定义的函数可以直接修改state中的数据,并且函数中只能编写同步代码,在组件中有两种使用方法:1. 直接使用 commit 来调用 2. 先使用 mapMutations 函数映射到组件中然后直接当作本地函数调用
2. 一般在 actions 函数中编写业务逻辑复杂或者异步的代码,然后在 actions 中再调用 mutations 来修改数据。在组件中有两种使用方法:1. 直接使用 dispatch 来调用 2. 先使用 mapActions 函数映射到组件中然后直接当作本地函数调用
题1、data 中定义的数据如何在页面中显示?
答:
1. 如果是字符串、数字等,使用 {{ ... }}
2. 如果是数组一般使用 v-for
# 题2、生命周期函数有哪些?项目中是怎么使用的?
答:
1. 创建前后、挂载前后、销毁前后、更新前后、被激活、被隐藏等
2. 项目中常用的:
created(创建后):调接口获取页面初始数据。
mounted(挂载后):在页面加载完之后执行一个 DOM 操作的 JS 代码,比如:商城后中的图表功能。
# 题3、在 Vue 中如何绑定事件?
答:v-on 或者 @ 。比如:v-on:click 或者 @click。
# 题4、什么是双向绑定?项目中是怎么使用的?
答:使用 v-model 指令进行双向绑定。
项目中:操作表单元素:单选框、复选框、下拉框等时需要定义数据并使用 v-model 进行绑定。
# 题5、Vue 组件中的 data 为什么是函数?
答:跟按引用传值有关。如果不用函数,data 是对象类型的数据,对象都是按引用传递的,会导致:当组件使用多次时,它们会共享用同一个 data 数据,修改任何一个组件中的 data ,其他的组件也会跟着一起改变,这是不对的,每个组件实例在使用时应该是完全独立 的,互不影响才对。
所以 data 必须是一个函数,每次在函数中返回一个全新的对象,这样就不会出现共享的问题了。
# 扩展:什么是按引用传值?
变量赋值时分为两种情况:
- 按值传递:如果是数字、布尔、字符串等基础类型。
原理:先把这个值在内存中复制一份,然后赋给另一个变量。
效果:赋值之后两个值是两个独立的变量互不影响 。
~~~
/* 按值传递 */
let a = 100 // 数字
let b = a // 按值传递(因为 a 是数字)
// 现在 a 和 b 是两个独立 互不影响 的变量
b=200 // 修改 b 不影响 a
console.log(a) // a 还是 100
~~~
- 按引用传递:如果值类型是对象或者数组时。
原理:把这个变量在内存中的地址赋给另一个变量。
效果:赋值之后,两个变量指向同一个内存地址,其实还是同一个变量,只不过有两个变量名。
~~~
/* 按引用传递 */
let a = [1,2,3] // 数组
let b = a // 按引用传递(数组和对象都是引用传递)
// a 和 b 指向内存中的同一个地址,a和b是同一个数据
b[0] = 100 // 把 b 修改
console.log( a[0] ) // 100 也变成100,
// 所以如果希望复制出一个全新的数组,需要使用 “克隆技术”
/*
浅克隆:只克隆最外层的数据。(只克隆一部分)
方法:let b = [...a] , 把 a 克隆一份给 b (浅克隆)
深克隆:把内层的也复制一份。(完全的克隆)
方法:let b = JSON.parse( JSON.stringify(a) )
*/
~~~
# 题6、如何定义计算属性?项目中是怎么使用的?
答:使用 computed 来定义计算属性。制作购物车时里面的商品总价。
# 题7、什么是监听器?监听什么的?项目中是怎么使用的?
答:使用 watch 来定义监听器,一个监听器就是一个函数,函数名就是要监听的 data 中的一个变量的名字,一旦监听的变量发生变量,这个函数就被调用了。
监听器分为浅监听和深度监听,当监听的数据是一个复杂的数据类型(数组、对象)时需要使用深度监听。
在项目中实现数据搜索、排序、翻页时使用过,每当用户点击翻页、排序、搜索条件的按钮时就要重新调用接口,所以我定义了一个变量,保存翻页、排序、搜索的信息,然后使用监听器监听这个变量,一量发生变化就重新调用接口获取数据
代码:
~~~
data() {
return {
// 保存翻页、搜索关键字、排序信息
info: {
page: 1,
keywords: '',
sortby: 'id',
sortway: 'desc'
}
}
},
// 监听器
watch: {
// 当条件改变时重新调用接口
info: {
deep: true, // 深度监听
handle: function() {
// 重新调用接口获取数据
}
}
}
~~~
# 题8、什么是过滤器?项目中是怎么使用的?如何使用过滤器?
答:使用 filter 定义过滤器。
过滤器分为全局过滤器 和 局部过滤器。
全局过滤器:在任何一个组件中可以直接使用。
局部过滤器:在组件中需要先引入,再注册到组件的 filter 中,然后才能使用。
当一个绝对时间(发表文章时间)在显示时转化成一个相对时间,项目中的实现思路:
1. 先定义了一个叫做 relativeTime 的全局变量器
2. 在组件中使用: {{ time | relativeTime }}
# 题9、什么是混入?项目中是怎么使用的?
答:mixins 是定义混入。可以把一段 JS 代码合并到一个 Vue 组件中。
用途:可以把多个组件共用的 JS 代码单独提取出来放到一个 JS 文件中,然后哪个组件中需要就直接混入。
之前写的商城后台的项目:使用了混入实现的把组件中的JS 代码和 HTML+CSS 分离写在两个文件中。
实现思路:
1. 把 JS 代码单独写到一个 JS 文件中
2. 在 .vue 文件中使用 mixins: [ js 文件] 混入进来
# 题10、如何将 data 中定义的图片路径绑定到 src 属性上?
答:使用 v-bind 或者 : 。
比如:
~~~
<img :src="image" />
<img v-bind:src="image" />
data:{
image: 'http://www.ww.ww/1.jpg'
}
~~~
# 题11、在 Vue 中使用一个自定义组件的流程是?
答:
自定义组件有两种情况:
- 全局组件:直接在页面中使用 ,比如: <Hello />
- 局部组件:先引入、再组件,然后才能使用。
1.创建一个自定义组件,比如:Hello.vue 组件
2. 使用 import 引入这个自定义组件,比如:import Hello from 'Hello.vue'
3. 注册这个组件,在 Vue 的 components: { Hello }
4. 使用:<Hello />
# 题12、组件之间如何传值?父向子传值时,如何设置属性的默认值和属性的类型?
答:
父>子 使用属性 ,注意,需要在子组件中使用 `props` 来接收属性。
子>父 使用 事件,注意:在子组件中使用 `this.$emit(事件名,数据)` 触发父组件中的事件
兄弟之间 使用 `Bus 总线` 的中间组件实现。
**父向子传值时,如何设置属性的默认值和属性的类型?**
在子组件中使用 props 接收属性:
写法一、不设置类型和默认值
props: [ 'name', 'age' ] // 接收 name 和 age ,不限制类型
写法二、限制类型和默认值
props: {
name: {
type: String,
default: ''
},
age: {
type: Number,
default: 0
}
}
# 题13、style 上的 scoped 是什么意思?使用原理是什么?
答:里面的样式只对当前这个组件生效,不会影响其他组件。
当添加了 scoped 之后,vue 会在这个组件中所有的标签上都添加一个唯一的标识符,只有这个组件中的标签上才有这个标签符,然后在 CSS 上会通过这个属性来限制 CSS 的应用范围。
1. 组件中添加属性
![](images/screenshot_1592444389455.png)
2. css 上通过属性来限制范围
~~~
/* 通过属性选择器就只对当前这个组件中的 .count 生效 */
.count [ data-v-26084dc2 ] {
color: blue;
}
~~~
# 题14、什么是插槽?干什么用的?
答:组件中会在特定的位置上留插槽,我们可以在使用这个组件时向这个插槽中添加HTML的结构,目的是可以自己扩展一个组件的功能。比如:项目中使用的 vant 的导航栏组件,但是它默认的图标不满足要求,所以我们可以使用它的插槽,自己来定义图标这个区域的内容。
插槽分为两种:
- 匿名插槽(默认插槽)一个组件中只能有一个匿名插槽。
如果在组件中间不使用 slot 默认放到匿名插槽的位置。
- 有名插槽,一个组件中可以有多个有名的插槽。
向有名插槽中放数据时需要使用 slot 属性指定插槽的名字
# 题15、v-if 和 v-show 的区别?
答:
共同:v-if 和 v-show 都是控制一个元素是否显示。
区别:v-if 如果是 false 就不渲染这个元素,页面中没有这个元素
v-show 无论 true 和 false 都会渲染这个元素,页面中始终有这个元素,当 false 时使用 display: none 把它隐藏。
# 题16、v-html 的用途是?
答:把一个 HTML 字符串 `解析` 成 HTML 显示出来。
比如:
~~~
{{ str }} ------------> 当成普通字符串显示出来: <h1>hello</h1>
<div v-html="str"></div> ----------> 解析 h1 标签,显示出来是一个加粗放大的 hello
data: {
str: '<h1>hello</h1>'
}
~~~
# 题17、template 组件的用途是?
答:这个标签在页面中不会渲染出任何元素,它的用途是把多个标签套起来当成一个整体。
比如有三个 div 标签要循环
~~~
<div>abc</div>
<div>abc</div>
<div>abc</div>
~~~
必须要使用一元素套起来
~~~
<div v-for="v in 10">
<div>abc</div>
<div>abc</div>
<div>abc</div>
</div>
~~~
这样最外层多了一个 div,如果不想要外层 div 可以使用 tempalte
~~~
<template v-for="v in 10">
<div>abc</div>
<div>abc</div>
<div>abc</div>
</template>
~~~
## $refs 是干什么用的?
Vue 提供给我们进行 dom 操作的一个属性。
在原生 JS 中当我们需要获取一个 DOM 元素时需要使用 `document.getElementById` 获取,那么在 Vue 中如何获取一个 DOM 元素呢?
示例代码:
~~~
// 在标签上添加一个 ref 属性
<div ref="hello">...</div>
// 在 vue 中通过 $refs 获取
this.$refs.hello
~~~