总结Ⅵ-框架篇之Vue

347 阅读12分钟

Vue

1. watch 和 computed 和 methods 区别是什么?

coumputed 是计算属性,methods是方法,watch是侦听属性

computed 和 methods 相比,最大区别是 computed 有缓存:如果 computed 属性依赖的属性没有变化,计算属性会立即返回之前的计算结果,而不必再次执行函数。而methods里的函数在每次调用时都要执行

watch 和 computed 相比,computed 是计算出一个属性,而 watch 则可能是做别的事情(如上报数据)

1 、watch无缓存;

2、watch适用于:需要在数据变化时执行异步或开销较大的操作时

computed不能执行异步任务,watch可以

2. Vue 有哪些生命周期钩子函数?分别有什么用?

beforeCreate created beforeMount mounted beforeUpdate updated beforeDestroy destroyed

翻译一遍,特别强调:mounted 组件被挂载到页面后,请求数据。(即在mounted周期内请求数据)

3. Vue 如何实现组件间通信?

  1. 父子组件之间通信:$emit('xxx',data) v-on:'xxx'=function(){}一个触发一个监听

  2. 爷孙组件之间通信:使用两次 v-on 通过爷爷爸爸通信,爸爸儿子通信实现爷孙通信

  3. 任意组件之间(包括兄弟组件,爷孙组件,父子组件):创建一个空的vue实例eventbus作为事件总线,通过eventbus触发,监听。

    var eventBus=new Vue()
    eventBus.$emit('xxx',data)     eventBus.$on('xxx',fn)
    
  4. 任意组件:通过Vuex通信。

这就引入了另一个问题:Vuex是怎么用的?

先说概念:Vuex是专门为vuejs设计的状态管理工具。核心是store仓库。然后介绍四个核心概念和作用

state: state对象里存储全局管理的状态,在组件里读取状态可以用计算属性,返回this.$store.state.x

getters: 当我们需要从state里的状态派生出一些状态时,可以在store里定义getters,它类似于计算属性,可以根据state里依赖的状态返回一个状态,也是有缓存的。在组件里读取getter,this.$store.getters.y

mutations: 在mutations里定义改变state状态的方法。这个函数接受state作为第一个参数,以操作state。触发它只有提交mutation,store.commit('xxx',x) Mutation 必须是同步函数

actions: 类似于mutations,不同之处是action可以是异步的。actions是通过提交mutation间接改变状态。在组件里this.$store.dispatch('xxx') 调用action,action里又提交mutation里的方法,从而更改状态。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
  actions: {
    increment ({ commit }) {  // 解构写法
        commit('increment')
    }
}
})

modules: 把store分割成模块。如果应用很复杂,store对象也会很复杂。

4. Vue 双向绑定/数据响应式怎么做到的?

怎么实现双向绑定?v-model ,在input元素上绑定一个内部数据到value属性,实现改变内部数据UI上的值也改变;监听input事件,把用户输入的值赋给内部数据。v-model就是这两句代码的语法糖。实现双向数据绑定。v-model="x"x是内部数据。 它等价于::value="x" @input="x=$event.target.value"

双向绑定/数据响应式的原理?当我们把普通的JS对象传入vue实例的data选项里,Vue会遍历它的所有属性,使用 Object.defineProperty 把这些属性全部转为 getter/setter,这样Vue就能监听这些属性,对这些属性的更改都会通知vue,进而渲染UI。

但是有一个问题,受Object.defineProperty的限制,Vue 不能检测到对象属性的添加或删除,解决方法是手动调用 Vue.set 或者 this.$set向嵌套对象添加响应式 property

5. Vue.set 作用

为嵌套对象添加响应式属性。Vue不允许动态添加根级响应式属性。但是可以用Vue.set为嵌套对象添加响应式属性。

直白一点就是,当你需要添加或删除属性的时候需要用Vue.set

6. Vue Router 怎么用的

Vue Router 是Vuejs官方的路由管理器

常用的组件:

  1. <router-link to='/xxx'> router-link组件用来导航,通过to属性指定路由。还有一个active-class属性,路由被激活后拥有的类名。
  2. <router-view> 是路由的出口, 不同的路由匹配到对应的组件,渲染在router-view的位置
  3. route是指当前被激活的路由对象,它包含了这个路由的所有状态信息。 常用:this.$route.params 它是一个键值对的对象,表示当前的路由参数。可以和动态路由匹配结合使用,:语法表示动态路由参数,这个参数就能通过params这个对象拿到。
  4. 编程式的导航:导航除了可以用router-link(声明式导航)外,还可以用router实例上的方法实现导航。this.$router.push('./xxx') 导航到不同的url,而且会向history添加一个新纪录,所以点击浏览器的后退按钮时,会返回到之前的url。this.$router.replace('/xxx') 和push一样,导航到指定的url,但是不会向history添加记录,所以点击后退按钮,会回到上上个url。 this.$router.go(1) 在浏览器记录里前进或后退几步

关键概念:

  1. 怎么实现路由懒加载?把不同的路由对应的组件分割成不同的代码块,然后当路由被访问的时候再去加载它对应的组件。实现代码:import('./money.vue')

  2. 什么是导航守卫?导航守卫通俗来说就是在路由跳转的过程中,提供给我们的一些钩子函数。vue router提供的导航守卫主要是用来通过跳转或取消的方式来守卫导航。有几种机会植入到导航过程中:全局的,单个路由独享的,组件内的。

    全局的: 全局前置守卫 beforeEach 在跳转之前被调用;全局后置钩子 afterEach 在跳转完成之后被调用; 全局解析钩子 beforeResolve

    单个路由独享的: beforeEnter 进入这个路由之前被调用,直接定义在某个路由配置里。

    组件内的: beforeRouteEnter 进入该组件对应的路由之前被调用;beforeRouteUpdate 路由正在改变,而且依然渲染该组件时被调用,比如在具有动态路由参数的路由之间跳转,都会映射到同一个组件;beforeRouteLeave 导航离开该组件对应的路由之前被调用

    三个参数:to:要跳转到的目标路由对象;from:当前即将要离开的路由对象;next:是一个函数,一定要调用一次next函数来完成当前的钩子,否则就无法继续下一个钩子。

  3. history模式:vue router默认是hash模式。可以设置成history模式,但是要有后台的配置支持。

7. Vue v-for循环中key的作用

key的作用主要是给每个节点一个唯一的标识,通过这个唯一的key,可以在新旧虚拟DOM的diff算法中,更快的找到对应的节点,从而提升效率

传统的diff算法是遍历新旧虚拟DOM树中的每一个节点,Vue的diff算法是只比较同一层级中的节点,然后再比较他们的子节点。这样效率更高。那么在比较子节点的时候,会先从首尾依次两两比较,再通过key比较,相当于一个map映射。这时key的作用就出来了:如果没有key,就只能通过遍历的方式比较来匹配节点,增加性能消耗。明显,map映射找节点要比遍历的速度更快。

所以在Vue中,不绑定key并不会有问题,只会有警告。但是绑定了key,会提升性能。

8. vue router中 hash模式和history模式的原理,区别

小白回答:hash模式url带#号,history模式不带#号。

大牛解答:

形式上:hash模式url里面永远带着#号,开发当中默认使用这个模式。如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url,适合推广宣传;

功能上:比如我们在开发app的时候有分享页面,那么这个分享出去的页面就是用vue或是react做的,咱们把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,但是使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人配合,让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上就ok了

9. v-for 和 v-if 同时使用的问题

Vue告诉我们,永远不要把 v-if 和 v-for 同时用在同一个元素上。

一般我们在两种常见的情况下会倾向于这样做:

  1. 为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
  2. 为了隐藏本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上 (比如 ul、ol)。

因为v-for比v-if的优先级更高。 所以在同一个元素上出现,v-for会先起作用。如果是上边两种场景,就会造成不必要的遍历。每次渲染都要遍历整个列表一次,然后再判断条件。就会非常低效。

场景一,换成遍历计算属性,是有缓存的,如果依赖的原始列表users不变,就不会重新计算。而且遍历的也是经过过滤后的列表,就是只遍历满足条件的,不需要像之前那样遍历所有项。

场景二,把v-if放到外层容器,就可以只检查一次,首先判断是否满足条件,不满足就直接不会v-for遍历。不用像之前那样,对每一项都检查一次。

10. v-ifv-show 的区别

相同点:它们都是控制DOM元素的显示和隐藏

不同点:

  1. 实现方式不同:v-if是通过判断后边数据的真假值,如果为真,就显示,在DOM树上添加一个节点,如果是假,就隐藏,把这个节点从DOM上移除。所以v-if是不断把节点从DOM树上添加/移除来控制显示和隐藏的。在第一次渲染时,如果后边的值是假,就什么都不做。只有在条件第一次变为真的时候,才开始局部编译。 而v-show在第一次渲染,不管后边条件是否为真,都会被编译,存在于DOM树中。他是通过切换CSS,来控制元素的显示和隐藏。这个元素始终存在于DOM树上,占据一个位置。如果后边条件为假,就隐藏,通过修改display为none,来隐藏。所以它只是在修改元素的CSS样式。
  2. 性能消耗:正因为v-if要进行DOM的添加和删除,所以切换消耗更高,不适合需要频繁切换的场景。v-show更适合频繁切换,就是初始渲染消耗高。

11. vue 父子组件生命周期的执行顺序

构造过程

父组件beforeCreated

父组件created

父组件beforeMount

子组件beforeCreated

子组件created

子组件beforeMount

子组件mounted

父组件mounted


销毁过程

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

12. <keep-alive>

keep-alive 是Vue内置的组件之一, 主要用于保留组件状态或避免重新渲染。


作用

在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验。


基础使用

最基础的一般是结合动态组件去使用:

<keep-alive>
    <component :is="currentView"></component>
</keep-alive>

new Vue({
    el: '#app',
    data(){
        return {
            currentView: Test //Test为引入的子组件
        }
    }
})

<component :is="currentView"></component> 也是一个内置组件,通过is属性指定不同的组件名,实现组件的切换。也就是动态组件。但是切换后再切回来,原来的组件就会被重新渲染。所以在外边包裹keep-alive ,就可以让里边被切换的组件被缓存。不用再次渲染。


生命周期钩子

被包含在 <keep-alive> 中创建的组件,会多出两个生命周期的钩子: activateddeactivated

activated 在组件被激活时调用,在组件第一次渲染时也会被调用,之后每次keep-alive激活时被调用。

deactivated 在组件被移除时调用。

注意:只有组件被 keep-alive 包裹时,这两个生命周期才会被调用,如果作为正常组件使用,是不会被调用,以及在 2.1.0 版本之后,使用 exclude 排除之后,就算被包裹在 keep-alive 中,这两个钩子依然不会被调用!另外在服务端渲染时此钩子也不会被调用的。


提供 include 和 exclude 属性

include - 字符串或正则表达式。只有名称匹配的组件会被缓存。

exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。

max - 数字。最多可以缓存多少组件实例。

<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。当匹配条件同时在 include 与 exclude 存在时,以 exclude 优先级最高


结合路由使用

<router-view> 展示路由匹配到的组件。有些时候可能需要将整个路由页面一切缓存下来,也就是将 <router-view> 进行缓存。这种使用场景还是蛮多的。

<!-- 一个include解决了,不需要多写一个标签,也不需要在路由元中添加keepAlive了 -->
<keep-alive include='a'>
    <router-view></router-view>
</keeo-alive>

13. vue2 和 vue3实现数据响应式的区别

  1. vue2是通过Object.defineProperty()对data上的每个属性设置getter/setter,来实现对每个属性的监听和拦截。这种方法有一个缺点,必须预先知道有哪些key,才能监听。所以对于新增的key,是无法实现响应式的。 vue3是通过proxy这个API实现数据响应式的,它是在更高维度上拦截属性的,它直接监听并拦截整个data对象,所以不管是已有的key还是新增的key,都能响应式。 提升效率

  2. vue2 只能通过重写数组原型上的方法来实现响应式,但是vue3 可以直接监听数组的变化。

14. Vue.nextTick

首先,它的用法:在下次DOM更新循环结束之后,执行一个延迟回调。在修改数据之后立即使用这个方法,可以获取更新以后的DOM。

Vue在实现数据响应式的时候,并不是我改变数据后,DOM就立即变化。Vue在更新DOM的时候是异步执行的。如果监听到数据变化了,Vue会开启一个任务队列,然后在下一次的事件循环中再执行,也就是再更新DOM。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

在修改数据后,马上读取,读到的还是旧值。在Vue.nextTick里读,就能读到新值了。因为它里边的回调会在DOM更新后被执行。

也可以使用this.$nextTick() 用法相同

15. 绑定事件中,@click、@click.native和@click.self有什么区别

@click

就是给html元素绑定事件


@click.native

是给组件绑定原生事件的,只能用在组件上,不可以用在原生元素上。@click.native 监听组件根元素的原生事件 。

如:父组件A,子组件B

// 父组件:
<template>
    <B @click="c"/>
</template>
methods:{
    c(){}
}

在子组件的身上监听,如果只写@click,点击B是不会触发事件c的,监听不了。

一种解决方法是在子组件的里边,某一个元素上监听事件@click,如果点击了,触发一个函数a,a里边用$emit手动触发click函数,然后在父组件那里,子组件的身上再用@click监听。

使用.native 修饰符,就可以使代码变得简洁。子组件里边,不再需要监听,触发。只需要在父组件那里,用@click.native ,这样点击B,事件处理函数c能被触发了。

<template>
    <B @click.native="c"/>
</template>

@click.self

<div @click.self="c">
    aaa
</div>

只有e.target是当前这个元素时,才会触发函数。冒泡或捕获经过它,都不会触发函数。