1.v-show和v-if指令的共同点和不同点
当条件改变的时候v-if是通过DOM节点的添加和删除来控制节点的显示隐藏的,
v-show是通过css样式控制节点的显示隐藏的。
v-if的切换成本比较高,v-show渲染成本比较高,
所以当切换频繁的时候使用v-show,如果初次渲染数据较多的时候,使用v-if
2.vue组件中data为什么必须是一个函数
vue组件可能存在多个实例,如果使用对象形式定义data,可能会导致它们共用一个data对象,那么状态变更会影响所有组件实例,这是不合理的采用函数的形式定义,在initData时会将作为工厂函数返回全新的data对象,有效避免了多个实例之间状态污染的问题。而在vue根实例创建过程中不存在这个限制,也是因为根实例只能有一个,不需要担心这种情况。
如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data(类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据)
Object是引用数据类型,里面保存的是内存地址,单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果
3.$nextTick的使用
vue的$nextTick其本质是对js执行原理EventLoop的一种应用。
$nextTick的核心是利用promise,mutationObserver、setImmediate、setTimeout的原生
javascript方法来模拟对应的微/宏任务的实现,本质是为了利用JavaScript的这些异步回调任务队列来实现vue框架中自己的异步回调队列。
nextTick是典型的将底层的javascript执行原理应用到具体案例中的示例,引入异步更新队列机制的原因:
- 如果是同步更新,则多次对一个或者多个属性赋值,会频繁触发dom的渲染,可以减少一些无用的渲染。
- 同时由于virtualDom的引入,每一次状态发生变化之后,状态变化的信号会发送给组件,组件内部使用virtualDom进行计算得出需要更新的具体的dom节点,然后对dom进行更新操作,每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要。
- vue采用了数据驱动的思想,但是在一些情况下,仍然需要操作dom。有时候,可能遇到这样的情况,dom1的数据发生了变化,而dom2需要从dom1中获取数据,那这时候就会发现dom2的试图并没有更新,这时就需要用到nextTick了。
4.Vue中双向数据绑定是如何实现的
vue2.0
vue2.0的双向数据绑定是通过数据劫持,结合发布订阅的方式来实现的,也就是说数据和视图同步,数据发生变化,视图也会发生变化,视图变化,数据也会随之发生改变。vue2的双向数据绑定核心是object.definedproperty()方法。给vue中的数据绑定get和set方法,当获取数据的时候,调用get方法,当修改数据的时候调用set方法。通过watcher监听器去更新视图,完成数据的双向绑定。
vue3.0
Object.defineProperty 的问题:在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。目前只针对以上方法做了hack处理,所以数组属性是检测不到的,有局限性Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
Proxy的两个优点:可以劫持整个对象,并返回一个新对象,有13种劫持。
5.v-if和v-for的优先级
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染。
v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名。
在 v-for 的时候,建议设置key值,并且保证每个key值是独一无二的,这便于diff算法进行优化。
在vue中,永远不要把v-if和v-for同时用在同一个元素上,会带来性能方面的浪费。
vue 2.x:当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级
vue 3.x:当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级。
6.vue常用的修饰符
事件修饰符:
stop(阻止事件冒泡)
prevent(阻止默认行为)
self(只触发本身)
once(只触发一次)
captrue(事件捕获,与事件冒泡的方向相反,事件捕获由外向内。)
表单修饰符:
lazy(当光标离开标签之后,才会赋值给value)
trim(过滤掉两边的空格)
number(自动将用户输入的值转化为数值类型。)
键盘修饰符:
普通键(enter、tab、delete、space、esc、up…)
系统修饰键(ctrl、alt、meta、shift…)
鼠标修饰符:lefft,right,middle
7.自定义指令详解
除了我们常用的核心的默认内置的指令像是v-model,v-show,v-if等,vue也允许我们自定义指令,有的情况下,对普通dom元素进行底层操作,这个时候就需要自定义指令。
自定义指令时用来操作dom的,尽管vue推崇的是数据驱动视图的理念,但并不是所有的情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于任何dom操作,而且是可复用的。
自定义指令分为两种:一种是自定义的局部指令,一种是全局指令
8.vue slot
slot又名插槽,是vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而在一个标签元素是否显示,以及怎么显示是父组件决定的。
slot又分为三类,默认插槽,具名插槽,作用域插槽。
- 默认插槽:又称匿名插槽,当slot没有指定name属性值的时候,一个组件内只能有一个匿名插槽。
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽,剧名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件传递过来的数据决定如何渲染该插槽。
9.vue初始化页面闪动问题
当网速较慢,vue.js文件还没有加载完时,在页面上会显示{{message}}的字样,知道vue创建实例,编译模板时,dom才会被替换,所以这个过程屏幕是闪动的。
所以解决这个问题,需要在style样式中设置【v-cloak】{display:none}。在一般情况下,v-clock是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用。
但是在具有工程化的项目里,比如使用了webpack和vue-router的项目中,html结构只是一个空的div元素,剩余的内容都是由路由去挂载不同的组件完成的,所以不需要v-cloak。
10.vue更新数组时触发视图更新的方法
-
vue.set相应式新增与修改数据可以设置对象或数组的值,通过key或数组索引,可以触发视图更新。
-
vue.delete,删除对象或数组中元素,通过key或者数组索引,可以触发视图更新。
-
数组对象直接修改属性,可以触发视图更新
-
数组整体修改,可以触发视图更新
Vue包含一组观察数组变异的方法,使用它们改变数组也会触发视图更新不变异的方法:
-
- push() 向数组的末尾添加一个或多个元素,并返回新的长度。
-
- pop() 删除最后一个元素,把数组长度减 1,并且返回它删除的元素的值。
-
- shift() 把数组的第一个元素从其中删除,并返回第一个元素的值。
-
- unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
-
- splice() 向/从数组中添加/删除项目,然后返回被删除的项目。 该方法会改变原始数组。
-
- sort() 对数组的元素进行排序。
-
- reverse() 颠倒数组中元素的顺序。
不变异的方法:
- filter()
- concat()
- slice()
他们返回的是一个新数组,使用这些方法时,可以用新数组来替换原始数组
11.Vue2.0 和 Vue3.0 有什么区别
- 性能:Vue 3 比 Vue 2 更快,因为它采用了新的渲染引擎,这使得它在大型应用程序中更快。
- 语法:Vue 3 使用了更简单的语法,并移除了一些 Vue 2 中的不常用功能,这使得代码更容易维护和阅读。
- 设计:Vue 3 采用了更加模块化的设计,把各个组件的功能分离开,使得应用程序更加灵活和可扩展。
- TypeScript 支持:Vue 3 原生支持 TypeScript,可以更轻松地与其他 TypeScript 项目集成。
- ***position API:Vue 3 引入了 ***position API
- Vue2 在模板中如果使用多个根节点时会报错,
Vue3 支持多个根节点,也就是 fragment
12.Vue3带来了什么改变
vue3.0对vue的主要3个特点:响应式、模板、对象式的组件声明方式,进行了全面的更改,底层的实现和上层的api都有了明显的变化,基于Proxy重新实现了响应式,基于treeshaking内置了更多功能,提供了类式的组件声明方式。而且源码全部用typescript重写
13.reactive与ref的区别
-
从定义数据角度对比:
ref用来定义:基本类型数据
reactive用来定义对象(或数组)类型数据
备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。 -
从原理角度对比:
ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。 -
从使用角度对比:
ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。
reactive定义的数据:操作数据与读取数据:均不需要.value。
14.计算属性和watch以及methods的区别
- 作用机制上:
watch 和 ***puted 都是以 Vue 的依赖追踪机制为基础的,当某一个数据发生变化的时候,
所有依赖这个数据的相关数据,自动发生变化,也就是自动调用相关的函数去实现数据的变动
methods 里面是用来定义函数的,它需要手动调用才能执行。而不像 watch 和 ***puted 那样,
“自动执行”预先定义的函数,相比于 watch / ***pute;methods 不处理数据逻辑关系,只提供可调用的函数,类似储存函数的一个库。 - 从性质上
methods里面定义的是函数,你显然需要像"fuc()"这样去调用它(假设函数为fuc)。
***puted是计算属性,事实上和和data对象里的数据属性是同一类的(使用上)。
watch:类似于监听机制+事件机制,回调函数自动调用。
15.$route 和 $router 的区别
router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,包含了所有的路由包含了许多关键的对象和属性。例如history对象
route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
16.v-model的使用
表单控件在实际开发中是非常常见的,特别是对于用户信息的提交,需要大量的表单
Vue中使用v-model指令来实现表单元素和数据的双向绑定
-
案例的解析:
- 当我们在输入框输入内容时
- 因为input中v-model绑定了message,所以会实时将输出的呢容传递给message,message发生改变。
- 当message发生改变时,因为上面我们使用Mustache语法,将message的值参入到DOMZ中,所以DOM会发生相应的变化
- 所以通过v-model实现了双向绑定
17.v-on可以监听多个方法吗
可以
<div class="pa">
<button v-on='
{
click:dianji,
focus:focu,
blur:blu
}'>
监听多个方法的写法
</button>
</div>
18.vue中过滤器有什么作用及详解
过滤器分为全局过滤器和局部过滤器,
全局过滤器可以在任何组件调用,局部过滤器只能在当前组件调用,
过滤器使用|管道符调用。可以串联使用。
19.EventBus注册在全局上时路由切换时会重复触发事件,如何解决
建议在created里注册,在beforeDestory移出
在组件内的beforeRouteLeave中移除事件监听
20.vuex有哪几种属性
-
state
state:state类似容器,包含应用的大部分状态
一个页面只能有一个state
状态存储是响应式的
不能直接改变 state 中的状态,唯一途径显式地提交mutations -
mutations
mutations:更改store中的状态唯一方法就是提交mutation,它是同步的,每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。更改 Vuex 的 state 中的状态的唯一方法是提交 mutation。回调函数可以接受两个参数,第一个参数是state状态,第二个参数是 mutation 的载荷就是外面store.***mit 传入额外的参数。
mutations其实就相当于我们vue里面的methods,也是定义方法的,只不过这个方法可以在多个组件调用就是了。
使用mutations中的方法格式: this.$store.***mit(‘名称’) -
getters
getters:相当于state的计算属性,从state派生出一些状态 -
actions
actions:Action 类似于 mutation,不同在于:
action 提交的是 mutation,而不是直接变更状态;
action可以进行任何异步操作,mutation不可以; -
modules
modules:模块化管理,有多个state可以通过模块化import到index.js的module属性中,可直接进行调用。参考vue的组件化思想。
21.请详细说下你对vue生命周期的理解
vue2.0生命周期:
创建阶段
beforeCreate()创建前阶段,这个时候还不能使用data中的数据。
created()创建完成 最早可以使用data中的数据
挂载阶段
beforeMount:在挂载开始之前被调用: 相关的 render 函数首次被调用
mounted: 挂载完成,DOM节点挂载到实例上去之后调用该钩子
更新阶段
beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
updated: 数据更新完成并且DOM更新完成后调用销
销毁阶段
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用
destroved:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定
vue3.0
- 去掉了vue2.0中的 beforeCreate 和 created 两个阶段,同样的新增了一个 setup
- beforeMount 挂载之前 改名 onBeforeMount
- mounted 挂载之后 改名 onMounted
- beforeUpdate 数据更新之前 改名 onBeforeUpdate
- updated 数据更新之后 改名 onUpdated
- beforeDestroy 销毁前 改名 onBeforeUnmount,可在此进行清除定时器,进行事件监听,发布订阅。
- destoryed 销毁后 改名 onUnmounted
- errorCaptured 报错 改名 onErrorCaptured
总结
不难发现3.0中钩子函数最大的变化就是setup代替了2.0中的beforeCreate和created,而对应其它几个函数来说都是名称上发生了一些变化,其功能基本上还是一样的。
22.vue优点
- vue作为一款轻量级框架,门槛低,上手快,简单易学。
- vue可以进行组件化开发,数据与结构相分离,使代码量减少,从而提升开发效率,易于理解
- vue最突出的优势在于对数据进行双向绑定,使用虚拟DOM
- 相较于传统页面通过超链接实现页面跳转,vue会使用路由跳转不会刷新页面
- vue是单页面应用,页面局部刷新,不用每次跳转都请求数据,加快了访问速度,提升了用户体验
23.单页面应用和多页面应用区别及优缺点
单页应用 页面跳转---->js渲染
优点:页面切换快 缺点:首屏加载稍慢,seo差
多页应用 页面跳转---->返回html
优点:首屏时间快,seo效果好 缺点:页面切换慢
24.vue路由跳转传参的方式有哪些
在template中可以使用标签实现跳转。
- router-link 标签直接拼接参数实现跳转
<router-link to="/t?index=1">
<button class="btn btn-default">点击跳转</button>
</router-link>
- router-link 标签使用to属性结合query实现跳转,里面是参数字典
<router-link :to="{path:'/t',query: {index: 1}}">
<button class="btn btn-default">点击跳转</button>
</router-link>
- this.$router.push() (@click函数里面调用)
this.$router.push({path: '/路径?参数=1'})
接收参数
this.$route.query.index
- query和 params区别
- query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传,密码之类还是用params刷新页面id还在
- params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失
- VUE几种路由跳转几种方式的区别
-
this.$router.push:跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面
-
this.$router.replace:跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)
-
this.$router.go(n):向前或者向后跳转n个页面,n可为正整数或负整数
-
25.vue遇到的坑,如何解决的
axios封装和api接口的统一管理
- axios的封装,主要是用来帮我们进行请求的拦截和响应的拦截。
- 在请求的拦截中我们可以携带userToken,post请求头、qs对post提交数据的序列化等。
- 在响应的拦截中,我们可以进行根据状态码来进行错误的统一处理等等。
- axios接口的统一管理,是做项目时必须的流程。这样可以方便我们管理我们的接口,在接口更新时我们不必再返回到我们的业务代码中去修改接口。
26.vue封装通用组件方法思路
点击查看
27.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中
如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用。
28.Vuex中如何异步修改状态
actions和之前讲的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态(会互相干扰).
actions 里面放我们函数,函数里面的参数context ,可以帮我们提交的mutations。主要逻辑其实就是在组件内部,调用actions之后,actions里面又去调用mutations,而mutations又去修改了state。
mutations:{
increate(state){ //payload:携带参数
state.counter++
}
},
actions:{
//放函数,
increateAction(context){
context.***mit("increate")
}
}
import {useStore} from 'vuex'
import {onMounted} from "vue"
export default {
setup(){
const store=useStore()
onMounted(() => {
store.dispatch("getHomeMutidata")
}),
return{
}
}
29.刷新浏览器后,Vuex的数据是否存在?如何解决
vuex的数据在刷新浏览器后不会保存,
这是因为vuex实例store里的数据保存在运行内存中,页面刷新会重载vue实例,导致store里的数据丢失
解决方法:
- 下载持久化存储插件,例如:vuex-along,vuex-persist、vuex-persistedstate
- 使用localStorage 或者 sessionStroage
30.vuex中的辅助函数怎么使用
-
mapState
mapState 函数返回的是一个对象。通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 ***puted 属性。自从有了对象展开运算符,我们可以极大地简化写法:(使用扩展运算符) - mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
- 使用 mapMutations 辅助函数将组件中的 methods 映射为 store.***mit,其原理就是将this.montify 映射为this.$store.***mit(‘montify’)
- 在组件中使用 this.$store.dispatch(‘prodect’) 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用
31.vue插槽?
slot又名插槽,是vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而在一个标签元素是否显示,以及怎么显示是父组件决定的。slot又分为三类,默认插槽,具名插槽,作用域插槽。
默认插槽:又称匿名插槽,当slot没有指定name属性值的时候,一个组件内只能有一个匿名插槽。
具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
作用域插槽:默认插槽,剧名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,作用域插槽数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定,也就是说,作用域插槽的不同之处就在于,数据不在父组件身上,而是在子组件身上,且组件的结构和内容由父组件决定。作用域组件限定了组件内结构和数据的展示范围,以便在开发中我们可以根据一个组件而不断变换其中的内容和结构。
32.vue3中如何获取refs,dom对象的方式?
方式一:setup函数方法内,获取单个ref属性绑定的dom元素:
- 先定义一个空的响应式数据ref定义的
- setup中返回该数据,你想获取哪个dom元素,在该元素上使用ref属性绑定该数据即可
<template>
<h1 ref="box">Ref属性获取dom</h1>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup () {
let box = ref(null)
onMounted(function () {
console.log(box.value);
})
return {
box
}
}
}
</script>
方式二:获取多个ref属性绑定的dom元素。
- 使用ref绑定一个函数,在函数里把dom添加到数组里面
<template>
<h3 :ref="getlist" v-for="index in 3">我是一组元素{{index}}</h3>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'Ref',
setup () {
// 获取v-for遍历的元素
// 定义一个空数组,接收所有的LI
// 定义一个函数,往空数组push Dom
let listDom = []
const getlist = (el) => {
listDom.push(el)
}
console.log(listDom);
return {
getlist
}
}
}
</script>
总结:
-
单个元素: 先声明ref响应式数据,返回给模板使用,通过ref绑定数据
-
遍历的元素:先定义一个空数组,定一个函数获取元素,返回给模板使用,通过ref绑定这个函数
33.vue3中生命周期的和vue2中的区别?
vue2.0生命周期:
创建阶段
beforeCreate()创建前阶段,这个时候还不能使用data中的数据。
created()创建完成 最早可以使用data中的数据
挂载阶段
beforeMount:在挂载开始之前被调用: 相关的 render 函数首次被调用
mounted: 挂载完成,DOM节点挂载到实例上去之后调用该钩子
更新阶段
beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
updated: 数据更新完成并且DOM更新完成后调用销
销毁阶段
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用
destroved:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定
vue3.0
- 去掉了vue2.0中的 beforeCreate 和 created 两个阶段,同样的新增了一个 setup
- beforeMount 挂载之前 改名 onBeforeMount
- mounted 挂载之后 改名 onMounted
- beforeUpdate 数据更新之前 改名 onBeforeUpdate
- updated 数据更新之后 改名 onUpdated
- beforeDestroy 销毁前 改名 onBeforeUnmount,可在此进行清除定时器,进行事件监听,发布订阅。
- destoryed 销毁后 改名 onUnmounted
- errorCaptured 报错 改名 onErrorCaptured
总结
不难发现3.0中钩子函数最大的变化就是setup代替了2.0中的beforeCreate和created,而对应其它几个函数来说都是名称上发生了一些变化,其功能基本上还是一样的。
34.说说vue中的diff算法?
点击查看
35.说说 Vue 中 CSS scoped 的原理?
在vue文件中的style标签上,有一个特殊的属性:scoped。当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有style标签全部加上了scoped,相当于实现了样式的模块化。
vue中的scoped属性的效果主要通过PostCSS转译实现
PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom。
36.vue3中怎么设置全局变量?
在vue2中,我们知道vue2.x是使用Vue.prototype.xxxx = xxx 来定义全局变量,然后通过 this . xxxx=xxx来定义全局变量,然后通过this.xxxx=xxx来定义全局变量,然后通过this.xxx来获取全局变量。
但是在vue3中,这种方法显然不行了。因为vue3中在setup里面我们是无法获取到this的。
vue3.0依赖注入的方式注册使用全局变量
在 main.js 中
const app = createApp(App)
// 配置全局变量 页面中使用 inject 接收
app.provide('global',{
store,
axios
})
app.use()进行使用
采用这种方法在全局变量的创建上会更加的方便 , 而且不用担心会出现像axios在使用globalProperties设置为全局对象后丢失axios对象只剩 axios对象只剩axios对象只剩http之类的问题
vue3.0store仓库进行存放全局变量
创建store文件夹
store文件夹下创建index文件
state中定义状态或数据
const store = createStore({
state () {
return {
base:"",//基本数据
}
},
mutations: {
/*修改基础数*/
changeBase(state,base) {
state.base=base;
},
}
})
export default store;
37.Vue中给对象添加新属性时,界面不刷新怎么办?
vue2的响应式原理使用的是对象代理去实现的,对象代理中有一个get和set方法,当我们访问对象的时候就会触发get方法,当我们对对象中的值进行修改时会触发set方法。但是当我们给对象添加一个新的属性时对象代理是检测不到的,所以就会出现直接给对象添加属性响应式不生效的问题。
所以在vue中可以使用this.$set(对象名,‘属性名’,属性值)的方法去给对象添加属性,或者使用Vue.set(对象名,‘属性名’,属性值)的方法进行添加,添加之后的属性就带有响应式了
38.谈谈对Vue中双向绑定的理解?
vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
VUE双向数据绑定,其核心是 Object.defineProperty()方法,给Vue中的数据绑定get和set方法,当获取数据的时候,调用get方法,修改data中的数据的时候调用set方法,通过watcher监听器去更新视图,完成数据的双向绑定。
- 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,
给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
- ***pile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定
更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
3.watcher订阅者是observer和***pile之间通信的桥梁,主要做的事情是:1*.在自身实例化时往属性订阅器里面添加自己
2*.组件自身必须有一个update()方法.3*待属性变动dep.notice()通知时,能调用自身的update()方法,
并触发***plie中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合observer、***pile和watcher三者,通过observer来监听自己的model数据变化,
通过***pile来解析编译模板指令,最终利用watcher搭起来Observer和***pile之间的通信桥梁,达到数据变化,
视图更新,视图交互话变化,数据model变更的双向绑定效果。
39.为什么vue2和vue3语法不可以混用?
优先级:
出现重名自变量,会根据书写顺序进行页面的展示。
vue2中的data和vue3中的setup
40.vue3中setup函数如何进行组件通讯?
setup语法糖方式通信:
父传子——defineProps
1.父组件使用子组件,在子组件标签里面,通过标签属性的方式进行数据传递
<template>
<children :list="list"></children>
</template>
2.在子组件引入import { defineProps } from 'vue'
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
</script>
defineProps方法定义变量名进行接收,template模板中进行使用。
子传父——defineEmits
父组件在子组件标签上通过v-on绑定自定义事件,子组件通过 const emits = defineEmits([‘事件名’])进行接收。
在子组件B中通过defineEmits注册一个事件名,然后在子组件B的方法中通过emit传递给父组件A。在组件A中,就可以为一些元素添加该事件名绑定事件方法。然后组件A在script setup中写这个事件方法,传递的参数就是子组件B在emit传递过来的值。这个值我们赋值给const的ref定义的数据,从而动态修改数据。
<template>
<div>
<input v-model="value" type="text" placeholder="请输入" />
<button @click="handleAdd"> 添加 </button>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const value = ref('')
const emits = defineEmits(['add'])
const handleAdd = () => {
emits('触发的方法', 传递的参数)
}
</script>
setup函数嵌套方式通信
父传子props
父组件中使用子组件的标签
在 子组件 setup 方法内使用props 来接收父组件传过来的数据。
<template>
<div>
<Article :msg="name"></Article>
</div>
</template>
<script>
import Article from '@/***ponents/Article.vue'
export default {
***ponents: {
Article
},
setup() {
return {
name: '渐行渐远渐无书,水阔鱼沉何处问'
}
}
}
子传父 content
<template>
<div>
{{msg}}
<button @click="sendToParent">子组件向父组件传递数据</button>
</div>
</template>
<script>
export default {
props:['msg'],
setup(props, content) {
console.log(props.msg)
function sendToParent() {
content.emit('change')
}
return {
sendToParent
}
}
}
</script>
41.vue3,***position API 的优势?
***position
在 Vue3 ***position API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合),即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API,***positon API,将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去。
***position APi:
- setup
Vue3.0中一个新的配置向,值为一个函数
setup是所有***position API(组合API) ”表演的舞台“
组件中所有用到的:数据、方法等等,均要配置在setup中
- setup语法糖
语法糖里面的代码会被编译成组件setup()函数的内容,不需要通过return暴露声明的变量、函数以及import引入的内容,即可在template使用,并且不需要export default{}
- ref函数
作用:定义一个响应式的数据。
基本类型的数据:响应式依然是靠Object.defineProperty()的get和set完成的
对象类型的数据:内部“求助”了Vue3.0的一个新的函数------reactive函数
- reactive函数
作用:定义一个对象类型的响应式数据
- defineEmits
setup语法糖中引入defineEmits方法,传入数组,数组里面则是父组件传递的自定义事件名。
- 自定义hooks
ve3中还可以自定义 hooks, 将单个功能封装成单个的函数,在集成到组件中去复用! 将功能进行解耦,方便维护、复用、可读性强
42.shallowReactive和shallowRef的区别
shallowReactive:只处理对象最外层的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
43.provide与inject如何使用
概述:父子组件传参可以通过props和emit来实现,但是当组件的层次结构比较深时,props和emit就没什么作用了。vue为了解决这个提出了Provide / Inject;provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量.
作用:实现祖和后代组件间通信。
provide()函数
定义:提供一个值,可以被后代组件注入。
具体实现:
父组件有一个provide选项提供数据,后代组件有一个inject选项来开始使用这些数据。
provide(第一个参数是要注入的key,它可以是一个字符串或一个symbol;第二个参数是要注入的值(具体要传递给子孙组件的数据)。
import {reactive,provide} from "vue";
let person = reactive({name: '张三',age: 14});
provide('person',person);
inject()函数
定义: 注入一个由祖先(父)组件或整个应用提供的值
实现: 接收父(祖)组件传递过来的值.
inject(第一个参数,第二个参数(可选)):第一个参数是注入的key,来自父(祖)组件,它们两者是需要保持一致的
Vue会遍历父组件链,通过匹配key来确定所提供的值,如果父组件链上多个组件对同一个key提供了之,那么离得更近的将会覆盖链上更远的组件所提供的值
如果没有能通过key匹配到的值,inject()函数将返回undefined,除非提供一个默认值
第二个参数是可选的,即没有匹配到key时,使用默认值,它也可以是一个函数,用来返回某些创建起来比较复杂的值,如果默认值本身就是一个函数
那么必须将false作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
与注册生命周期钩子的API类似,inject()必须在组件的setup()阶段同步调用
import {inject,toRefs} from "vue";
const person = inject('person');
// 若是使用解构,则会丢失响应式,修改数据时,页面不会更新,具体解决,可以引入toRef或toRefs函数
const {name,website} = toRefs(person);
模板使用
{{person.name}}---{{person.website}}
总结
provide/inject 做全局状态管理的原则:
多人协作时,做好作用域隔离;
尽量使用一次性数据作为全局状态
44.toRaw 与 markRaw是什么作用?
- toRaw
作用:将一个由reactive生成的响应式对象转化为普通对象。
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新. - markRaw
作用:标记一个对象,使其永远不会再成为响应式对象。
应用场景:
1.有些值不应被设置为响应式的,例如复杂的第三方类的库。
2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
45.readonly 与 shallowReadonly谈谈你对他的理解?
- readonly: 让一个响应式数据变为只读的(深只读)。
readonly是一个函数,他会接收一个响应式的数据,如上定义的name变量,将该变量加工一番readonly函数
包裹后后重新返回一个name,这个重新返回的name变量中的所有的东西都不允许修改,所以我们现在再
点击上面的修改按钮 name 变量也不会有任何改变,并且控制台会有警告。 - shallowReadonly:让一个响应式数据变为只读的(浅只读)。
shallowReadonly只限制对象中的第一层数据(不能改动,如:salary),但是嵌套的深层次的value属性值
是可以更改的,我们点击更改按钮测试就能发现,被shallowReadonly包裹的对象的深层次值改变了。
46.自定义hook是什么?
在 Vue 3 中,Hook 是通过 Vue 3 新特性的最重要的部分——组合式 API 来实现的.
本质是一个函数。自定义hook,就是将setup中使用过的组合式函数进行封装,实现代码复用。
把数据、方法、和生命周期钩子封装在一个js文件。
在src目录下新建子目录hooks,并在hooks目录下新建文件:usePoint.js
47.虚拟DOM一定更快吗?
虚拟DOM/domDiff
我们常说的虚拟DOM是通过JS对象模拟出来的DOM节点,domDiff是通过特定算法计算出来一次操作所带来的DOM变化。react和vue中都使用了虚拟DOM,我们借着react聊聊虚拟DOM。
react中涉及到虚拟DOM的代码主要分为以下三部分,其中核心是第二步的domDiff算法:
把render中的JSX(或者createElement这个API)转化成虚拟DOM
状态或属性改变后重新计算虚拟DOM并生成一个补丁对象(domDiff)
通过这个补丁对象更新视图中的DOM节点
虚拟DOM不一定更快
干前端的都知道DOM操作是性能杀手,因为操作DOM会引起页面的回流或者重绘。相比起来,通过多一些预先计算来减少DOM的操作要划算的多。
但是,“使用虚拟DOM会更快”这句话并不一定适用于所有场景。例如:一个页面就有一个按钮,点击一下,数字加一,那肯定是直接操作DOM更快。使用虚拟DOM无非白白增加了计算量和代码量。即使是复杂情况,浏览器也会对我们的DOM操作进行优化,大部分浏览器会根据我们操作的时间和次数进行批量处理,所以直接操作DOM也未必很慢。
那么为什么现在的框架都使用虚拟DOM呢?因为使用虚拟DOM可以提高代码的性能下限,并极大的优化大量操作DOM时产生的性能损耗。同时这些框架也保证了,即使在少数虚拟DOM不太给力的场景下,性能也在我们接受的范围内。
而且,我们之所以喜欢react、vue等使用了虚拟DOM框架,不光是因为他们快,还有很多其他更重要的原因。例如react对函数式编程的友好,vue优秀的开发体验等,目前社区也有好多比较这两个框架并打口水战的,我觉着还是在两个都懂的情况下多探究一下原理更有意义一些
48.vue路由中,history和hash两种模式有什么区别?
- history模式
history API 是 H5 提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL 地址而不重新发起请求。
<a href="javascript:toA();">A页面</a>
<a href="javascript:toB();">B页面</a>
<div id="app"></div>
<script>
function render() {
app.innerHTML = window.location.pathname
}
function toA() {
history.pushState({}, null, '/a')
render()
}
function toB() {
history.pushState({}, null, '/b')
render()
}
window.addEventListener('popstate', render)
</script>
history API 提供了丰富的函数供开发者调用:
history.replaceState({}, null, '/b') // 替换路由
history.pushState({}, null, '/a') // 路由压栈
history.back() // 返回
history.forward() // 前进
history.go(-2) // 后退2次
history 模式的优缺点:
优点:路径比较正规,没有井号 #
缺点:兼容性不如 hash,且需要服务端支持,否则一刷新页面就404了。
- hash模式
hash 模式是一种把前端路由的路径用井号 # 拼接在真实 URL 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange 事件。
<script>
<a href="#/a">A页面</a>
<a href="#/b">B页面</a>
<div id="app"></div>
<script>
function render() {
app.innerHTML = window.location.hash
}
window.addEventListener('hashchange', render)
render()
</script>
在上面的例子中,我们利用 a 标签设置了两个路由导航,把 app 当做视图渲染容器,当切换路由的时候触发视图容器的更新,这其实就是大多数前端框架哈希路由的实现原理。
hash 模式的优缺点:
优点:浏览器兼容性较好,连 IE8 都支持
缺点:路径在井号 # 的后面,比较丑。