vue2面试题60题合集

1,539 阅读19分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

00 什么是Vue以及Vue的特点

vue是一套用于构建用户界面的渐进式(自底向上逐层应用)js框架

  1. 组件化开发
  2. 无需操作DOM
  3. 使用虚拟DOM和diff算法复用DOM节点

01 SPA及其优缺点

SPA:signal page application,单一页面应用。

一旦页面加载完成,不会因为用户在浏览器上的操作而进行跳转、刷新,取而代之的是利用路由机制实现的HTML内容的变换、UI和用户的交互,避免页面的重新加载。

数据需要通过ajax请求获取。

优点:

  1. 避免不必要的跳转和重复渲染,提高了用户的体验,减轻了服务器的压力。
  2. 前后端分离,使架构更加清晰。

缺点:

  1. 初次加载耗时长(可以通过按需加载来避免)。
  2. 浏览器的前进后退不可以使用,要自己建立堆栈来进行页面切换。
  3. SEO难度大。因为就一个页面。

02 v-if和v-show的区别

v-if是真正的条件渲染,不停地的进行销毁和重建,但若初始条件为假时,就什么也不做。

v-show无论初始渲染条件是否为真,都进行渲染,只是使用“display”属性来控制是否在页面上显示

03 class 和 style如何动态绑定

都可以通过对象和数组来绑定。

但是class里的对象写法使用于:绑定多个样式,样式个数和样式名字都不确定; 数组写法适用于:绑定多个样式,个数和名字都确定,但用不用不确定。

:class="{open:item.open}"

:style="[{backgroundImage:`url(${images[currIndex]})`},largePositoin]"
    images: {
      type: Array,
      default: () => []
    }

04 怎样理解Vue的单项数据流

父级的prop的更新会向下流动到子级的prop里,但是反过来不可以, 若想反过来,只能通过$emit派发一个自定义事件,父组件接到后由父组件修改

05 computed和watch的区别和应用场景

computed是计算属性,其结果具有缓存属性,只有它依赖的值发生变化,才会在下次获得computed的值的时候重新计算computed。一般在进行数值计算并且依赖他的结果时使用。

watch更多是“监视作用”,一般在异步执行或者开销较大时使用。 开销较大的操作指的是当数据发生变化时执行的复杂的业务逻辑。

06 生命周期

  1. beforeCreate: 组件实例创建之初,组件属性生效之前。
  2. created: 组件实例已经创建完成,属性也绑定完成,但是真实DOM还没有生成,$el还不可用。在这里发送axios请求
  3. beforeMount: 挂载开始之前被调用,相关render函数被首次调用。
  4. mounted: el被新创建的$el替换。在这个阶段才可以访问操作DOM。在这里操作DOM
  5. beforeUpdate: 组件更新前调用。
  6. update: 组件更新后调用。
  7. activated: keep-alive专属,组件被激活时调用。
  8. deactivated: keep-alive专属,组件被移除时调用。
  9. beforeDestory:组件销毁前调用。
  10. destoryed: 组件销毁后调用。

07 Vue父子组件钩子函数执行顺序

  • 加载渲染过程
    • 父组件bc=》父组件c=》父组件bm=》子组件bc=》子组件c=》子组件bm=》子组件m=》父组件m
  • 子组件更新过程
    • 父组件bu=》子组件bu=》子组件u=》父组件u
  • 父组件更新过程
    • 父组件bu=》父组件u
  • 销毁过程
    • 父组件bd=》子组件bd=》子组件d=》父组件d

08 为什么组件里的data必须是函数,而vue实例里的data可以用对象表示?

组件是为了复用,如果不将data写成函数的形式,那么没有作用域的隔离,子组件的data值会相互影响。

vue实例是不会被复用的,因此不存在引用对象的问题。

09 谈谈keep-alive的理解

本质是vue的一个内置组件,可以使被包含的组件保持状态,避免重新渲染。

当被包含的组件被激活时,触发activated钩子函数;当组件移除时,触发deactivated钩子

有两个使用场景:一个是动态路由,一个是router-view

image.png

10 组件间通信的几种方式:

d21dd36f38b12fe920a1947f64a8b6e.png

11 什么是MVVM模型

  • M(model,模型):数据模型,后端提供的API接口
  • V(view,视图):视图层,也就是用户界面,狭义的前端
  • VM(视图模型):Vue实例

12 数据代理

通过vm对象来代理data对象中属性的操作,可以更加方便的操作data。

基本原理:通过Object.defineProperty()将data对象中的所有属性添加到vm身上;为每一个添加到vm上的属性增加getter和setter方法;通过getter和setter来操作data对应的属性。

13 Vue中key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据产生新的虚拟DOM,之后新旧对比决定是否渲染。

  1. 旧虚拟DOM中有与新虚拟DOM相同的key

    • 若内容没变,则使用之前的真实DOM
    • 若内容改变,则渲染新的真实DOM并替换掉之前的
  2. 没有相同的key

    • 创建新的真实DOM并渲染

14 使用index作为key可能引发的问题

如果对数据进行逆序添加、逆序删除等破坏原本顺序的操作,就会产生不必要的更新。同时在进行这样的操作时,如果页面有输入类DOM,会产生错误的更新(例如input输入框)。

如果不进行破坏顺序的操作,仅仅是渲染,用index作为key是没有问题的。

一般开发时的key是通过npm下载库自动生成的(uuid,nanoid)

15 什么是ref,以及它和id的区别

ref:用来给元素或者组件注册引用信息

对于传统的HTML标签,ref和id属性的作用基本一样;对于组件标签,ref输出组件的实例对象,id输出组件的HTML结构 (通过console.log(this.$refs.sch)输出)

16 mixin

mixin 混入 混合 :复用配置

当混入里的数据和组件本身的数据发生冲突,以本身为主(data,methods)

但是生命周期钩子除外,发生冲突就两者都执行,先执行mixin的钩子,再执行组件本身的钩子

17 plugin

plugin 插件

可以在里面的install函数里写全局过滤器,混入,自定义指令,以及给vue原型添加方法等

18 $nextTick

正常函数里面修改data的语句很多,但不会每修改一次数据页面就重新渲染一次,而是会等函数执行完成后刷新一次页面。

而有些语句希望在页面渲染后执行,那么就把它包裹在this.$nextTick(function(){})里面。

$nextTick:在下一次DOM更新结束后执行其回调。

19 同源策略和跨域

同源策略:协议名,域名,端口号这三个必须一致

跨域:违背了同源策略(注意可以发送请求,服务器也返回了数据,但是接收不到)

解决跨域:

  • cors:后端配置(最优解)
  • jsonp:通过script标签的src属性在访问外部资源时不受同源策略的影响来配置,前端配置
  • 配置代理服务器:配置一个和当前端口号一致的代理服务器来当中介,即满足同源策略中的端口号。此时浏览器向配置的代理服务器发送请求,后代理服务器向目标服务器发送请求,服务器之前的请求不必遵循同源策略。

3dd0b42751edace02884c5c2f38ed64.png

20 路由

路由就是一组“key-value”对应关系,多个路由需要经过路由器的管理。

路由组件之间的跳转就是旧组件销毁与新组件挂载。

每个组件的vue组件实例对象(vc)都增加了两个新属性: route(路由本身的路径,参数等信息),route(路由本身的路径,参数等信息),router(路由器,有各种方法)

21 router-link标签的replace属性

作用:控制路由跳转时操纵浏览器历史记录的模式。

浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前历史记录。路由跳转时默认是push。

路由的历史记录本身的数据结构是栈,push时将每一个地址压进栈中,回退时再读取出来。replace时直接替换掉栈顶的地址。

22 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
  3. hash模式:
    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:
    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

23 对vue项目的优化

  • v-if和v-show区分使用
  • 为v-for增加key
  • computed和watch区分使用
  • 事件及时销毁
  • 图片懒加载
  • 路由懒加载
  • 第三方插件、组件库按需引入
  • SSR:服务器端渲染:页面上的内容是通过服务器渲染的,浏览器直接展示服务器返回的HTML就行了。首屏进行SSR
  • 浏览器缓存
  • CDN的使用(Content Delivery Network,内容分发网络,这是一项缩短时延的技术。当用户访问网站时,将其指向最近的缓存服务器)。大文件进行CDN引入。

24 虚拟DOM的原理及其优缺点

  1. 原理

    • 用JS对象模拟真实DOM,对真实DOM进行抽象
    • diff算法:比较两棵虚拟DOM树的差异
    • pach算法:将两个虚拟DOM对象的差异应用到真实DOM树
  2. 优点

    • 保证性能下限
    • 无需手动操作DOM
    • 跨平台(例如服务器端渲染(SSR))
  3. 缺点

    • 无法进行极致优化

25 Vue是如何实现双向数据绑定的

  1. 实现一个监听器,对数据对象进行遍历,利用Object.defineProperty() 对属性都加上 setter 和 getter。
  2. 修改属性时,触发setter。
  3. 读取属性时,触发getter。

自己实现: 在表单类标签上同时绑定v-bind:value='value'和@input事件(e.target.value)

26 路由的懒加载

正常加载:

import List from '@/components/list.vue'
const router = new VueRouter({
  routes: [
    { path: '/list', 
    component: List }
  ]
})

懒加载方式1:使用箭头函数+import的方式,这也是官方生成的方式。

const router = new VueRouter({
  routes: [
    { 
      path: '/list',
      component: () => import('@/components/list.vue') 
    }
  ]
})

懒加载方式2:箭头函数+require

const router = new Router({
  routes: [
   {
     path: '/list',
     component: resolve => require(['@/components/list'], resolve)
   }
  ]
})

27 scss 是什么?有哪几大特性?

css的预编译。

特性:可以使用变量,例如($变量名=值)

28 is这个特性你用过吗?主要在哪个方面?

  1. 动态组件
<component :is="componentName"></component>

componentName可以是在本页面已经注册过的组件,或者是全局组件。通过控制componentName可以达到动态切换组件的效果。

有些 HTML 元素,诸如 <ul>、<ol>、<table>和<select>,对于哪些元素可以

出现在其内部是有严格限制的。

而有些 HTML 元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特

定的元素内部。

<ul>

<card-list></card-list>

</ul>

所以上面<card-list></card-list>会被作为无效的内容提升到外部,并导致最

终渲染结果出错。应该这么写:

<ul>

<li is="cardList"></li>

</ul>

29 在vue生命周期、hook或者其他option里面为什么不使用箭头函数?

因为箭头函数没有定义this上下文,而是绑定到其父函数的上下文中。所以在vue中使用箭头函数,那么箭头函数的this并不会指向vue实例。

30 nextTick

如果需要在刷新DOM后对数据修改,就要用到nextTick。

原理:vue框架通过异步队列控制DOM更新和nextTick回调函数的执行顺序。nextTick源码里面有许多isNative()判断。

31 如何对Vue首屏加载实现优化?

  1. Vue路由懒加载
  2. Vue组件尽量不要全局引入
  3. 把不常改变的的库放在index.html中,通过CDN引入
  4. 首页单独做服务端渲染
  5. 使用更轻量级的工具库
  6. 开启gzip压缩

32 Vue中怎么重置data?

使用Object.assign(),将所有可枚举的属性的值,从一个或者多个源对象复制到目标对象。且返回目标对象。

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }
使 用 Object.assign() , vm.$data 可 以 获 取 当 前 状 态 下 的 data ,

vm.$options.data(this) 可 以 获 取 到 组 件 初 始 化 状 态 下 的 data , 复 制

Object.assign(this.$data, this.$options.data(this))// 注意加 this,不然取不到 data()

33 Vue 中操作 data 中数组的方法中哪些可以触发视图更新?不可以的话有什么办法?

可以触发视图更新的:push,pop,unshift,shif,reverse,sort,split。

不可以的话,可以使用"this.$set()"

this.$set(Array, index, newValue)

34 Vue单向数据流

数据从父级流向子级,只能单向绑定。

子组件不可以直接修改父组件中的数据,如果想修改,可以通过$emit派发一个自定义事件,父组件接收到后,由父组件修改。

这样是为了防止子组件意外修改父组件中的数据,导致数据流向难以理解,且状态难以管理。

35 style加上scoped的原理?

在Vue组件中,为了使模块私有化,不对全局样式造成污染,可以在style标签上添加一个scoped属性。当这个Vue项目的所有组件都加上scoped之后,相当于实现了样式的私有化。

原理:这种效果通过PostCSS转译实现,给div标签增加一个 data-v-唯一id 的属性

转译后:
<div data-v-fed36922>Vue.js scoped</div>.scoped[data-v-fed36922]{font-size:14px;}

36 请说出vue-cli项目中src文件夹中每个文件夹/文件的用法。

  1. assets 存储静态资源
  2. router 定义路由相关配置
  3. util 存放工具。例如:request.js,对axios请求进行二次封装
  4. store 定义vuex相关配置
  5. components 存放可重用组件
  6. view 存放路由组件
  7. app.vue 主组件
  8. main.js 入口文件

37 使用vue时造成页面卡顿,需要怎么解决?

针对于首次加载卡顿的问题,需要用到路由懒加载,简单来说就是按需加载。

像vue这种单页面应用,如果不使用懒加载,那么webpack打包后,文件就会变的很大,进入首页时,加载时间就会变长且造成白屏。

对于页面首屏,最好使用SSR,也就是服务器端渲染的方式,在服务器上渲染页面,浏览器直接展示返回的HTML。

38 在Vue中使用插件的步骤

  • 采用ES6的import...from...,或者CommonJS的require()方法引入插件。
  • 之后使用Vue.use(plugin)使用插件。

39 vue2兼容IE的哪个版本?

不支持IE8及以下,部分兼容IE9,完全兼容10及以上。

因为vue2的响应式原理是基于js的Object.defineProperty(),所以不支持IE8及以下。

40 Vue渲染模板时,怎么保留模板中的HTML注释?

在组件中将comments选项设置为true

<template comments> ... <template>

41 对vue的template编译的理解?

  • 先将模板通过编译器转化成AST(抽象语法树)
  • 将AST转化为字符串的过程会得到render函数
  • 执行render函数,返回值就是virtual Node

42.页面第一次加载触发哪几个钩子?

beforeCreate,created,beforeMount,mounted。

如果是父组件:

beforeCreate,created,beforeMount,子组件(beforeCreate,created,beforeMount,mounted),mounted。

43.vue常用的修饰符

事件修饰符

  • .prevent阻止默认事件,比如a标签的跳转。
  • .stop阻止单击事件冒泡。
  • .self当event target是元素本身才可以触发。
  • .capture事件侦听,事件发生时会调用。
  • .once只触发一次。
  • .native自定义组件触发原生事件,比如自定义组件想触发@click事件。

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击

表单修饰符:

  • .lazy
  • .trim
  • .number

image.png

44.常用指令

image.png

45.vue如何定义一个过滤器

可以全局注册,Vue.filter('过滤器名称',回调函数)

46.如何理解Vuex

vuex是一个专门为vue.js应用开发的状态管理库。能够集中存储所有组件的状态(数据)。并且可以通过状态来驱动组件的变化。

  • state:状态,也就是数据存放在这里。
  • mutations:在这里改变state里的数据,也只有这里能修改state里的数据。注意,mutations里面只能放同步逻辑。
  • actions:存放异步逻辑代码,操作mutations改变state里的状态。
  • getters:相当于计算属性,一般用来对state里的数据进行一次简单的封装,方便在组件中调用。
  • modules:为了更加直观的查看状态,可以多建几个小型的vuex模块,最后在主vuex模块里面通过modules导出。

47.vuex的工作流程

  1. 在组件中使用dispatch触发vuex里面的actions里面的异步函数
  2. 在actions里使用commit触发mutations里的同步函数
  3. 在mutations的同步函数里面直接修改state里的数据
  4. 数据修改完毕后,传导给页面,页面的数据也会发生变化

48.说一下你在学vue时踩过的坑

  1. 使用数组方法修改数组,无法驱动视图层发生变化。因为只有那七个方法vue底层进行了重写,其他的方法需要使用Vue.set()。究其根本,是因为vue2的响应式系统使用的是Object.defineProperty无法检测到数组里面的变化。v3使用了proxy对响应式系统进行了重写,可以对整个数组数据进行深度监视。

vue.set的用法

Vue.set(object, key, value)

<div id="app">
	<ul>
		<li v-for="(item, index) in tempList" :key="index">{{item}}</li>
	</ul>
	<button @click="addList">change</button>
</ div>

<script>
new Vue({
    el:"#app",
    data:{
        tempList: [
        {id: 1, name: '1'},
        {id: 2, name: '2'}
      ]
    },
    methods:{
        addList () {
            Vue.set(this.tempList, 0, {id: 3, name: '3'})
        }
    }
});
</script>

在组件中使用,就不能直接使用vue实例

export default {
    data:{
        tempList: [
        {id: 1, name: '1'},
        {id: 2, name: '2'}
      ]
    },
    methods:{
        addList () {
            this.$set(this.tempList, 0, {id: 3, name: '3'})
        }
    }
};
</script>

  1. 对于this.$nextTick()的使用。

49.v-for和v-if能不能在一起使用

最主要的原因是因为每次在循环的时候都进行判断,会大大的浪费效率

在vue2中v-for的优先级比v-if的优先级要高。

在vue3中v-if的优先级比v-for要高。

解决方案:在代码外面使用template标签包裹,先使用v-if判断,不合适就不渲染,大大节省效率。

50.vue是如何处理对象和数组的响应式的

数组,给七个方法底层进行重写,当数据发生改变的时候就驱动视图层发生改变。其他的数组方法就使用Vue.set()

对象,使用Object.definProperty给属性添加getter和setter来检测。

51.如何处理vue项目中的错误

接口错误

对asiox进行二次封装,封装request.js时,在接口的响应时进行处理

image.png

对response的第二个error参数的回调进行处理

逻辑错误

设置全局错误处理函数

Vue.config.errorHandler = function (err, vm, info) {
  // handle error
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  // 只在 2.2.0+ 可用
}

errorCaptured

errorCaptured是 2.5.0 新增的一个生命钩子函数,当捕获到一个来自子孙组件的错误时被调用

基本类型

(err: Error, vm: Component, info: string) => ?boolean

例子:

定义一个父组件cat

Vue.component('cat', {
    template:`
        <div>
			<h1>Cat: </h1>
        	<slot></slot>
        </div>`,
    props:{
        name:{
            required:true,
            type:String
        }
    },
    errorCaptured(err,vm,info) {
        console.log(`cat EC: ${err.toString()}\ninfo: ${info}`); 
        return false;
    }

});

定义一个子组件kitten,其中dontexist()并没有定义,存在错误

Vue.component('kitten', {
    template:'<div><h1>Kitten: {{ dontexist() }}</h1></div>',
    props:{
        name:{
            required:true,
            type:String
        }
    }
});

页面中使用组件

<div id="app" v-cloak>
    <cat name="my cat">
        <kitten></kitten>
    </cat>
</div>

在父组件的errorCaptured则能够捕获到信息

cat EC: TypeError: dontexist is not a function
info: render

51.vue权限管理怎么做

接口权限

登录后拿token,将token存储起来,做一个持久化存储。通过axios进行拦截,每次请求的时候都带上token。如果登录过期就跳转到登录页面。

axios.interceptors.request.use(config => {
    config.headers['token'] = cookie.get('token')
    return config
})
axios.interceptors.response.use(res=>{},{response}=>{
    if (response.data.code === 40099 || response.data.code === 40098) { //token过期或者错误
        router.push('/login')
    }
})

路由权限

初始化的时候先挂载不需要权限控制的路由,比如登录页,404等错误页。如果用户通过URL进行强制访问,则会直接进入404,相当于从源头上做了控制

登录后,获取用户的权限信息,然后筛选有权限访问的路由,在全局路由守卫里进行调用addRoutes添加路由

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '@/utils/auth' // getToken from cookie

NProgress.configure({ showSpinner: false })// NProgress Configuration

// permission judge function
function hasPermission(roles, permissionRoles) {
  if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) >= 0)
}

const whiteList = ['/login', '/authredirect']// no redirect whitelist

router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  if (getToken()) { // determine if there has token
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => { // 拉取user_info
          const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()//
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})

菜单权限

菜单和路由都由后端返回

const list = [
      {
        path: "/user",
        name: "user",
        label: "用户管理",
        icon: "user",
        url: "UserManage/UserManage"
      },
      {
        label: "其他",
        icon: "location",
        path: "/other",
        children: [
          {
            path: "/page1",
            name: "page1",
            label: "页面1",
            icon: "setting",
            url: "Other/PageOne"
          },
          {
            path: "/page2",
            name: "page2",
            label: "页面2",
            icon: "setting",
            url: "Other/PageTwo"
          }
        ]
      }
    ];

在将后端返回路由通过addRoutes动态挂载之间,需要将数据处理一下,将component字段换为真正的组件

按钮权限

使用v-if

获取用户权限role和路由表里的meta.btnPermissions,然后再做判断

52.说一下vue项目的目录结构

  1. 文件夹和文件夹内部的文件语义一致性。
  2. 紧耦合的文件应该放到一起,以相对路径引入。
  3. 公共文件应该以绝对路径从根目录引入。
  4. src外的文件不应该被引入。

52.vue项目如何打包部署上线

  1. npm run build 生成dist文件夹。
  2. 使用ftp工具将dist上传到服务器的/home/www目录下。
  3. 刷新浏览器/重启ngix(是一个高性能的 HTTP 和 反向代理 web 服务器),就能看到浏览器可以访问网站了。

公司里面主要使用jenkins自动化测试和自动打包上线。

53.谈谈对vue响应式的理解

数组和对象类型的值发生变化的时候,通过defineReactive方法,借助defineProperty,将所有属性添加getter和setter方法。

缺陷:只能监控最外层的属性,如果是多层的,就要进行全量递归。

get里面会做依赖搜集,set里面会做数据更新。

依赖搜集:当取值的时候,搜集watcher,放到dep里面;当用户更改值的时候,通知watcher,去更新视图。

54.vue的生命周期钩子是如何实现的?

vue的生命周期钩子是回调函数,在创建组件实例的过程中会触发相应的钩子。 内部会对钩子进行处理,将钩子维护成数组的形式。

55.既然vue可以通过数据劫持精准探测数据变化,为什么还要使用diff算法检测差异?

数据劫持是通过definePorperty添加getter和setter方法,如果都采用数据劫持的方式来探测数据变化,性能会变得很差。

使用diff,算法粒度没那么细,只会更新当前组件,不会更新当前组件的子组件。如果粒度过细,会导致更新不准确。

56.谈谈对组件的理解

  • 组件化开发能大幅提高应用开发效率、测试性、复用性
  • 常用的组件化技术:属性、自定义事件、插槽
  • 降低更新范围,只重新渲染变化的组件
  • 高内聚、低耦合、单向数据流

57.描述组件渲染流程

产生虚拟节点=》创建真实节点=》插入到页面

58.vue中使用了哪些设计模式

  • 单例模式,new多次,只有一个实例。
  • 工厂模式:传入参数就可以创建实例。例如虚拟节点的创建。
  • 发布订阅这模式:eventBus。
  • 中介者模式:vuex。中介者模式主要用来降低组件间通信的复杂性。

59.路由模式

vue-router有三种模式:hash,history,abstract。

  • abstract在不支持浏览器的API换景使用
  • hash模式兼容性好,但是不美观,不利于SEO
  • history美观,historyAPI+popState,但是刷新会出现404

60.vue-router有几种钩子/守卫?

image.png