热搜: fiddler git ip 代理
历史搜索

面试题整理之Vue篇

游客2024-12-20 07:30:02
目录文章目录
  1. 前言
  2. 基础面试题
  3. 底层面试题
  4. 结束语

前言

现在对于前端来说,问的最多的可能是JavaScript基础,除此之外问的更多的就是目前的主流框架相关的一些知识点。目前主流的框架Vue、React、Angular,国内的互联网情况来讲,目前还是Vue居多。

就目前来看越来越多的面试,和之前的面试不太一样了,两三年前问的更多的是对于主流框架的应用,目前更多的是对于底层的一些问题更多一些。针对这些Vue相关面试题做一下总结并记录。希望可以帮到更多的小伙伴。

基础面试题

基础面试题即Vue应用和使用方面的面试题,这一部分大多数还是面向于初级工程师,相对来说要简单一些。

问:Vue 生命周期钩子都有哪些?

答:Vue生命周期一共有10个:

  1. beforeCreate – 在实例初始化之前调用,在此生命周期中是不存在this的,因为Vue实例还没有创建。
  2. created – 实例初始化之后调用
  3. beforeMount – 在挂载开始之前被调用,如果是在服务器端渲染时不被调用,由于元素还没有渲染,在此声明周期无法获取到DOM元素。
  4. mounted – 在挂载后被调用,是可以获取元素,并进行操作
  5. beforeUpdate – 视图层数据更新前调用,在虚拟DOM更新之前,可以获取到DOMj 节点,但是此节点为更新之前的DOM节点
  6. updated – 视图层数据更新后调用,此处获取到的DOM节点是更新之后DOM
  7. beforeDestroy – 实例销毁之前调用,在被销毁的组件中进行调用
  8. destroyed – 实例销毁后调用

beforeDestroydestroyed生命周期阶段可以在此阶段,进行`Vuex数据重置,取消事件监听等操作,提升性能。

以上是比较常用的生命周期钩子函数,还有两个比较特殊的生命周期钩子,分别是activateddeactivated,这两个生命周期只有在组件上使用了keep-alive之后才会被执行。

  1. activated – 实例被激活时使用,用于重复激活一个实例的时候,该生命周期于会在mounted之后执行。
  2. deactivated – 实例没有被激活时

问:父组件和子组件生命周期钩子函数执行顺序?

答:

Vue的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

加载渲染过程

  1. 父 beforeCreate
  2. 父 created
  3. 父 beforeMount
  4. 子 beforeCreate
  5. 子 created
  6. 子 beforeMount
  7. 子 mounted
  8. 父 mounted

子组件更新过程

  1. 父 beforeUpdate
  2. 子 beforeUpdate
  3. 子 updated
  4. 父 updated

父组件更新过程

  1. 父 beforeUpdate
  2. 父 updated

销毁过程

  1. 父 beforeDestroy
  2. 子 beforeDestroy
  3. 子 destroyed
  4. 父 destroyed

问:在哪个生命周期内调用异步请求?

答:

可以在钩子函数createdbeforeMountmounted中进行调用,因为在这三个钩子函数中,data已经创建,可以将服务端端返回的数据进行赋值。推荐在 created 钩子函数中调用异步请求,因为在created钩子函数中调用异步请求,因为能更快获取到服务端数据,减少页面loading时间,ssr不支持beforeMount 、mounted钩子函数,所以放在created中有助于一致性。

问:父组件可以监听到子组件的生命周期吗?

答:

可以的,有两种方法可以可以做到,第一种则是使用$emit自定义事件进行事件监听,当子组件执行到某个生命周期时用$emit通知即可。第二种使用@hook:生命周期名称@hook方法不仅仅是可以监听mounted,其它的生命周期事件,例如:createdupdated等都可以监听。

问:keep-alive 是做什么的?

答:keep-aliveVue内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

  1. 一般结合路由和动态组件一起使用,用于缓存组件
  2. 提供includeexclude属性,两者都支持字符串或正则表达式, include表示只有名称匹配的组件会被缓存,exclude表示任何名称匹配的组件都不会被缓存 ,其中exclude的优先级比include
  3. 对应两个钩子函数activateddeactivated,当组件被激活时,触发钩子函数activated,当组件被移除时,触发钩子函数deactivated

问:组件之间的传值通信方式有哪些?

答:组件间通信方式有 7 种:

1. props/emit

父组件传入属性,子组件通过props接收,可以通过这种方法获取到父组件传入的参数。而子组件则是可以通过$emit('时间名',参数)像外暴露一个自定义事件,在父组件中的属性监听事件,同时也能获取子组件传出来的参数,使用v-on:事件名称=func或者@事件名称=func的方式监听子组件的自定义事件。

还可以通过prop接收函数为参数,子组件调用该函数,子组件执行函数时,可以传入对应的实参,在父组件接收时以形参的形式接收,执行对应的逻辑,可以达到通信的目的。

2. EventBus

EventBus又称为事件总线,EventBus来作为沟通桥梁可以使任意两个组件之间通讯,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件。

如何使用EventBus,使用new Vue的方式获得Vue实例,实质上EventBus是一个不具备DOM的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便。

可以把获取到Vue实例挂载到Vue.prototype中也可以存放到一个单独的js文件中导出,使用哪种取决于业务的具体情况,一般推荐使用第二种方法。

使用EventBus.$emit("事件名称", 参数);的方式进行事件发布。使用EventBus.$on("事件名称", func)的方法对事件进行订阅。func的参数则是对应事件传递过来的参数。

EventBus的原理是把注册的事件存起来,等触发事件时再调用。定义一个类去处理事件。

3. Vuex 状态管理

VuexVue的核心插件、用于任意组件的任意通讯,需注意刷新后有可能Vuex数据会丢失。

创建全局唯一的状态管理仓库store,有同步mutations、异步actions的方式去管理数据,有缓存数据getters,还能分成各个模块modules易于维护

4. ($parent|$root)/$children

这种通信方式推荐使用,这样会使两个组件之间强耦合在一起,对于以后的维护和拓展来说不不是特别的友好。

通过($parent|$root)/$children获取到对应的组件实例,调用组件内部的方法以达到通信的效果。

5. $ref

通过$ref引用的方式获取子节点,常用于父组件调用子组件的方法,在$refs来存储当前所有设置了ref属性的组件的实例,在实例中可以调用到组件内部的方法。

6. attrs/listeners

$attrs可以获取父组件传进来但没有通过props接收的属性,可以使用v-bind="$attrs"的形式继续向子组件传递参数。

$listeners会展开父组件的所有监听的事件,一个页面中有两个组件的点击事件触发方法是一样的。可以使用v-on="$listeners"把事件继续向子组件传递事件。

7. provide/inject

父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。只要在父组件中调用了,那么在这个父组件生效的生命周期内,所有的子组件都可以调用inject来注入父组件中的值。

问:watch 和 computed 有什么区别?

答:

computed是计算属性在使用时和data对象中的数据属性是类似的,watch是用于监听某一个属性的变化的,computed擅长的是一个数据受多个数据影响,然而watch是用于监听某一个属性的变化的。

computed支持缓存只有依赖数据发生改变,才会重新进行计算,computed基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值,watch则不支持缓存,数据变,直接会触发相应的操作。watch的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;

computed不支持异步,computed内有异步操作时无效,无法监听数据的变化,watch支持异步。

computed在使用时,computed属性值是函数,那么默认会走get方法,函数的返回值就是属性的属性值,在 computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。watch在使用时,监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数。immediate(组件加载立即触发回调函数执行),deep(深度监听)为了发现对象内部值的变化,复杂类型的数据时使用。

computed内部就是根据Object.definedProperty()实现的,computed具备缓存功能,依赖的值不发生变化,就不会重新计算。watch是监控值的变化,值发生变化时会执行对应的回调函数,computedwatch都是基于Watcher类来执行的。computed缓存功能依靠一个变量 dirty,表示值是不是脏的默认是true,取值后是false,再次取值时dirty还是false直接将还是上一次的取值返回。

问:为什么 data 必须是一个工厂函数而不能是一个对象?

答:

因为Vue组件复用原则,由于data是对象为引用类型,当一个组件的数据改变后另一个组件状态会受到影响,然而通过工厂函数的形式返回对象就不会导致这样的问题,因为每个工厂函数所返回的对象都是一个全新的对象相对独立。

问:Vue 中 style scoped 有作用?

答:

vue文件中的style标签上,有一个特殊的属性:scoped。当一个style标签拥有scoped属性时,它的 CSS 样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。scoped会在DOM结构及css样式上加上唯一性的标记data-v-something属性,即CSS带属性选择器,以此完成类似作用域的选择方式,从而达到样式私有化,不污染全局的作用。scoped使用虽然方便但是我们需要慎用,因为在我们需要修改公共组件(三方库或者项目定制的组件)的样式的时候,scoped 往往会造成更多的困难,需要增加额外的复杂度。使用>>>可以穿透scoped属性,修改其他第三方组件的样式。使用sassless的样式穿透/deep/

问:v-show 与 v-if 有什么区别?

答:

v-if是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建,v-if是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。v-show就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSSdisplay属性进行切换。所以,v-if适用于在运行时很少改变条件,不需要频繁切换条件的场景,v-show则适用于需要非常频繁切换条件的场景。

问:当给 data 中的对象或数组数据发生变更数据没有视图没有响应是什么原因?

答:

因为在Vue在初始化时,会对data中的数据使用object.defineproperty对数据进行挟持,通过get/set完成数据响应的操作,由于添加的属性,在Vue在初始化时该属性不存在于data中,所以该属性没有挟持到无法执行get/setVue提供过this.$set方法可以对新增加的数据挟持完成数据相应。

问:SPA 和 SSR 有什么区别?

答:

SPA应用是现在最常用的开发形式即单页面应用,页面整体式javaScript渲染出来的,称之为客户端渲染CSRSPA渲染过程由客户端访问URL发送请求到服务端,返回HTML结构(但是SPA的返回的HTML结构是非常的小的,只有一个基本的结构)。客户端接收到返回结果之后,在客户端开始渲染HTML,渲染时执行对应javaScript最后通过JavaScript渲染成HTML,渲染完成之后再次向服务端发送数据请求,注意这里时数据请求,服务端返回json格式数据。客户端接收数据,然后完成最终渲染。

SSR渲染流程是这样的,客户端发送URL请求到服务端,服务端读取对应的URL的模板信息,在服务端做出HTML和数据的渲染,渲染完成之后返回HTML结构,客户端这时拿到的之后首屏页面的HTML结构。所以用户在浏览首屏的时候速度会很快,因为客户端不需要再次发送ajax请求。并不是做了SSR我们的页面就不属于SPA应用了,它仍然是一个独立的SPA应用。

SSR是处于CSRSPA应用之间的一个折中的方案,在渲染首屏的时候在服务端做出了渲染,注意仅仅是首屏,其他页面还是需要在客户端渲染的,在服务端接收到请求之后并且渲染出首屏页面,会携带着剩余的路由信息预留给客户端去渲染其他路由的页面。

SSR 优点:

  1. 更好的 SEO,搜索引擎爬虫爬取工具可以直接查看完全渲染的页面
  2. 更宽的内容达到时间(time-to-content),当权请求页面的时候,服务端渲染完数据之后,把渲染好的页面直接发送给浏览器,并进行渲染。浏览器只需要解析html不需要去解析javaScript

SSR 缺点:

  1. 开发条件受限,Vue组件的某些生命周期钩子函数不能使用
  2. 开发环境基于Node.js
  3. 会造成服务端更多的负载。在Node.js中渲染完整的应用程序,显然会比仅仅提供静态文件server更加占用CPU资源,因此如果你在预料在高流量下使用,请准备响应的服务负载,并明智的采用缓存策略

问:vue-router 有几种模式?

答:

vue-router公共三种模式:hashhistoryabstract

hash 模式:

  1. hash值是url#及后面的部分
  2. hash值改变不会引起页面刷新
  3. hash值的改变会触发hashchange事件

history 模式:

  1. 利用history.pushState来完成url跳转而无需刷新页面
  2. 需要后台配置支持,如果URL匹配不到任何静态资源,服务器应该返回应用依赖的index.html页面

abstract 模式:

  1. 适用于所有JavaScript环境,例如服务器端使用Node.js
  2. 没有浏览器API,路由器将自动被强制进入此模式
  3. 这个历史记录的主要目的是处理SSR,通过调用router.pushrouter.replace将该位置替换为启动位置

问:$route 和$router 的区别?

答:

$router

通过Vue.use(VueRouter)Vue构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由,包含了许多关键的路由信息,还有路由的跳转方法,钩子函数等。

$route

$route是一个跳转的路由对象,每一个路由都会有一个$route对象,是一个局部的对象,可以获取对应的namepathparamsquery等。路由对象的属性是只读的,里面的属性是immutable(不可变)的,不过可以使用watch监测路由的变化。

问:vue-router 守卫都有哪些?

答:

vue-router守卫主分为三类守卫分别是:全局守卫、路由守卫和组件守卫

全局守卫

全局守卫字面量理解就是定义在全局钩子函数,当路由触发跳转操作时则会触发对应的钩子函数,全局守卫有三种:

  1. beforeEach:它在每次导航路由跳转前触发
  2. beforeResolve: 所有组件内守卫异步路由组件被解析之后触发
  3. afterEach:路由跳转完成后触发

beforeEach全局前置守卫接收三个参数:

  • to: 即将要进入的目标路由对象
  • from: 当前导航正要离开的路由对象
  • next: 一定要调用该方法不然会阻塞路由

重点说一下next参数,next参数是一个函数,可以不添加,但是一旦添加,则必须调用一次,否则路由跳转等会停止。next参数只有在beforeEachbeforeResolve两个路由守卫中存在,afterEach因为已经跳转到了目标路由则没有提供next方法。如果直接执行next函数则会直接接入to所对应的目标路由中。next(false)则会终端当前路由当行,返回form路由对应的路由中。在next中传入跳转方案(如:next('/') 或者 next({ path:'/'})),则会跳转到对应的地址中。next(error)则导航会终止,且该错误会被传递给router.onError()注册过的回调。beforeEach可以定义多个,会根据创建顺序调用,在所有守卫完成之前导航一直处于等待中。

路由守卫

路由守卫只有一个beforeEnter,需要在路由配置上定义beforeEnter守卫,此守卫只在进入路由时触发,在beforeEach之后紧随执行,不会在paramsqueryhash改变时触发,beforeEnter路由守卫的参数是tofromnext,同beforeEach一样。

组件守卫

组件守卫则是需要定义在组件中的,组件守卫石油三种:

  1. beforeRouteEnter:路由进入组件之前调用,该钩子在全局守卫beforeEach和路由守卫beforeEnter之后,全局beforeResolve和全局 afterEach之前调用,该守卫内访问不到组件的实例,也就是thisundefined,也就是他在beforeCreate生命周期前触发
  2. beforeRouteUpdate:在当前路由改变,但是该组件被复用时调用,举例来说,对于一个带有动态参数的路径/foo/:id,在/foo/1/foo/2之间跳转的时候,由于会渲染同样的Foo组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。可以访问到组件实例 this
  3. beforeRouteLeave:导航离开该组件的对应路由时调用,可以访问组件实例 this

问:vue-router 如何做权限控制?

答:

vue-router权限控制最常用的方法是在beforeEach需要跳转的路由进行控制,如果没有权限则无法跳转到路由,一般情况下在路由配置的meta中添加权限控制字段,当路由发生跳转时,会触发beforeEach全局路由守卫,在该守卫中对权限进行鉴别即可完成路由权限控制。初次之外还有一种方法,则是使用addRoutes方法,动态的向vue-router中添加路由配置,在添加配置之前只在路由中注册具有权限的路由信息即可。

问:vue-router 传参有几种方式?

答:

vue-router传参一共有三种方式:

param

param传参需要在配置路由信息的是时候,在路由中注明需要接收的参数(如:url/:id),其中的id则是param需要传递的参数,需要注意的是当以这种形式传递参数的时候,如果没有传递参数是无法正确的匹配到路由的。可以在定义参数前添加?则代表该参数可有可无,无论是否传递参数都能正确的匹配到对应的路由。参数后面可以添加正则如:url/?:id(\d+),以这种形式添加参数可以限制参数的格式。

当使用param的时候,在配置路由信息的时添加{props:true}配置项,则传入的param参数,在页面的中的props中接收到该参数,除了以这种方式接收以外,还可以使用this.$route.params中获取到参数。传递param参数无论使用router-link还是编程式导航,如果是传入的字符串形式,则可以在对应的路由地址后面直接拼接参数即可。如果是以对象的形式,在对象中添加params字段,内部对应的是对应的param参数即可。

query

queryparam传参只是有略微的不同,query传参不需要在路由配置中定义有哪些参数,query是可有可无的不会影响到路由地址的匹配。query的传递参数使用query对象或者在地址后面以?的形式进行拼接。接收的时候则是在this.$route.query中获取到传递过来的参数。

注:无论是使用 param 还是 query 进行路由传参,当页面刷新的时候,都不会导致参数丢失,因为其参数是直接存放于路由地址中

meta

个人不太建议使用meta这种形式传参,虽然通过操作也是可以完成传参的目的,但是当页面刷新的时候会导致参数的丢失,但是,这种传参是隐式传参,用户是无法得知的。

使用meta传参可以在路由跳转之前,在跳转页面通过this.$route获取到当前路由的对象,在路由对象的meta中存入对应的参数。然后使用对应的方法进行路由跳转。当跳转完成之后,在目标页面使用beforeRouteEnter组件钩子函数,对参数进行接收,虽然beforeRouteEnter无法访问到this,但是在第三个参数即next中可以传入一个函数作为参数,这个参数中可以访问到当前组件的实例,在之后在beforeRouteEnter的第二个参数form中对象中可以读取到meta中存储的数据完成传参。

你对 Vuex 是如何理解的?

答:

Vuex是为Vue提供的状态管理模式,集中式存贮管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。中有五大核心属性:

  1. state:state主要用于存储数据和存储状态,在根实例中注册store以后,用this.$store.state来访问到state中所存储的数据。存放数据方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会对应的更新。
  2. mutation:更改Vuexstore中的状态的唯一方法是提交mutation
  3. action:该方法与mutation类似,唯一不同的是在action所执行的是异步方法。
  4. getter:可以认为是store的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
  5. module:将store分割成模块,每个模块都具有statemutationactiongetter甚至是嵌套子模块。

当组件进行数据修改的时候我们需要调用dispatch来触发actions里面的方法。actions里面的每个方法中都会有一个commit方法,当方法执行的时候会通过commit来触发mutations里面的方法进行数据的修改。mutations里面的每个函数都会有一个state参数,这样就可以在mutations里面进行state的数据修改,当数据修改完毕后,会传导给页面。页面的数据也会发生改变。

由于传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递虽然也可以通过其他的方法进行参数传递,可能仍会感到无力。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致代码无法维护。所以我们需要把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的视图,不管在树的哪个位置,任何组件都能获取状态或者触发行为!另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。

底层面试题

这一部分面试题相比上面要相对难一些,可能直接上升了一个等级,可能需要对Vue相关API有足够的了解,并且阅读过相关源码的下才能更好的理解。

问:Vue 的两个核心是什么?

答:分别是:数据驱动和组件系统

数据驱动

Vue数据观测原理在技术实现上,利用的是ES5Object.defineProperty和存储器属性:gettersetter可称为基于依赖收集的观测机制,这些gettersetter对用户来说是不可见的,但是在内部它们让Vue能够追踪依赖,在property被访问和修改时通知变更。核心是VM,即ViewModel,保证数据和视图的一致性。每一个指令都会有一个对应的用来观测数据的对象,叫做watcher,每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把接触过的数据property记录为依赖。getter的时候我们会收集依赖,依赖收集就是订阅数据变化watcher的收集,依赖收集的目的是当响应式数据发生变化时,能够通知相应的订阅者去处理相关的逻辑。setter的时候会触发依赖更新,之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。

组件系统

组件系统是Vue的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树,Vue更希望构建的页面是由各个组件构成而非html,当开发项目时把整个应用程序划分为组件,以使开发更易管理与维护。

组件系统中包含了几个很重要的部分:

  1. template:模板声明了数据和最终展现给用户的 DOM 之间的映射关系。
  2. data:一个组件的初始数据状态。对于可复用的组件来说,这通常是私有的状态
  3. props:组件之间通过参数来进行数据的传递和共享
  4. methods:对数据的改动操作一般都在组件的方法内进行
  5. lifecycle hooks:一个组件会触发多个生命周期钩子函数
  6. assets:Vue.js 当中将用户自定义的指令、过滤器、组件等统称为资源

问:如何理解 MVVM?

答:

MVVM主要由三个部分组成:ModelViewViewModel

  1. Model:代表数据模型,也可以在 Model 中定义数据修改和业务逻辑
  2. View:代表 UI 组件,它负责将数据模型转化成 UI 展现出来
  3. ViewModel:代表同步 View 和 Model 的对象

MVVMViewModel没有直接的关系,全部通过ViewModel进行交互。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回ModelMVVM本质就是基于操作数据来操作视图进而操作DOM,借助MVVM无需直接操作DOM,开发者只需完成包含声明绑定的视图模板,编写ViewModel中有业务,使得View完全实现自动化。

问:Vue 初始化做了什么?

答:

Vue在初始化时会接收用户所传入的options即所需要的参数,之后则会创建Vue的实例,并且为该实例定义一个唯一的_uid的标识,用于区分Vue的实例,把Vue实例标记成Vue避免被观察者观测到。

完成实例创建的初始化工作之后,需要对用户选项和系统默认的选项进行合并,首先处理的的是组件的配置内容,把传入的option与其构造函数本身进行合并,这里蚕蛹的策略是默认配置和传入配置的配置进行合并。如果是内部组件(子组件)实例化,且动态的options合并会很慢,需要对options的一些特殊参数进行处理。如果是根组件,将全局配置选项合并到根组件的配置上,其实就是一个选项合并。

接下来则是初始化核心部分,首先初始化Vue实例生命周期相关的属性,定义了比如:rootparentchildrenrefs,接着初始化自定义组件事件的监听,若存在父监听事件,则添加到该实例上,然后初始化render渲染所需的 slots、渲染函数等。其实就两件事

  1. 插槽的处理、
  2. $createElm 也就是 render 函数中的 h 的声明

接下来则是执行beforeCreate钩子函数,在这里就能看出一个组件在创建前和后分别做了哪些初始化。

执行完beforeCreate钩子函数之后,初始化注入数据,隔代传参时先inject。作为一个组件,在要给后辈组件提供数据之前,需要先把祖辈传下来的数据注入进来。对propsmethodsdatacomputedwatch进行初始化,包括响应式的处理,在把祖辈传下来的数据注入进来以后再初始化provide

最后调用created钩子函数,初始化完成,可以执行挂载了,挂载到对应DOM元素上。如果组件构造函数设置了el选项,会自动挂载,所以就不用再手动调用$mount去挂载。

问:如何理解 Vue 的响应式数据?

答:

Vue中实现了一个definedReactive方法,方法内部借用Object.definedProperty()给每一个属性都添加了get/set的属性。definedReactive只能监控到最外层的对象,对于内层的对象需要递归劫持数据。数组则是重写的 7 个pushpopshiftunshiftreversesortsplice来给数组做数据拦截,因为这几个方法会改变原数组。对象新增或者删除的属性无法被set监听到只有对象本身存在的属性修改才会被劫持。或使用Vue提供的$set()实现监听,在defineReactive中存有一个Dep类,这个用来收集渲染的WatcherWatcher的主要工作则使用用来更新视图。

问:Vue 如何进行依赖收集?

答:

Dep是一个用来负责收集Watcher的类,Watcher是一个封装了渲染视图逻辑的类,用于派发更新的。需要注意的是Watcher是不能直接更新视图的还需要结合Vnode经过patch()中的diff算法才可以生成真正的DOM

然而每一个属性都有自己的dep属性,来存放依赖的Watcher,属性发生变化后会通知Watcher去更新。在用户获取(getter)数据时Vue 给每一个属性都添加了dep属性来(collect as Dependency)收集Watcher。在用户setting设置属性值时dep.notify()通知收集的Watcher重新渲染。Dep依赖收集类其和Watcher类是多对多双向存储的关系。每一个属性都可以有多个Watcher类,因为属性可能在不同的组件中被使用。同时一个Watcher类也可以对应多个属性。

每一个属性可以有多个依赖,比如这个属性可能使用在computed中,watch中,本身的data属性中。这些依赖都是使用响应式数据的Dep 来收集的。Watcher是依赖就像一个中介,能够被Dep收集也能够被Dep通知更新。

问:Vue 是如何编译模板的?

答:

编译是把我们写的.vue文件里的template标签包含的html标签变为render函数,可以这么理解template模板是对render的封装,templatevue源码里面会变转化为render函数:

  1. 将 template 模板字符串转换成 ast 语法树(parser 解析器),这里使用了大量的正则来匹配标签的名称,属性,文本等。
  2. 对 AST 进行静态节点 static 标记,主要用来做虚拟 DOM 的渲染优化(optimize 优化器),这里会遍历出所有的子节点也做静态标记
  3. 使用 AST 语法树 重新生成 render 函数代码字符串 code。

问:Vue 生命周期钩子实现原理?

答:

Vue中的生命周期钩子只是一个回调函数,在创建组件实例化的过程中会调用对应的钩子执行。使用Vue.mixin({})混入的钩子或生命周期中定义了多个函数,Vue内部会调用mergeHook()对钩子进行合并放入到队列中依次执行。

问:Vue.mixin 使用场景和原理?

答:

Vue.mixin主要用于抽离一个公共的业务逻辑实现复用。其内部执行时会调用mergeOptions()方法采用策略模式针对不同的属性合并。混入的数据和组件的数据有冲突就采用组件本身的。Vue.mixin({})存在一些缺陷,导致混入的属性名和组件属性名发生命名冲突,数据依赖的来源问题。

问:$nextTick 实现原理?

答:

vm.$nextTick(cb)是一个异步的方法为了兼容性做了很多降级处理依次有promise.then,MutationObserversetImmediatesetTimeout。在数据修改后不会马上更新视图,而是经过set方法notify通知Watcher更新,将需要更新的Watcher放入到一个异步队列中,nexTick的回调函数就放在Watcher的后面,等待主线程中同步代码执行借宿然后依次清空队列中,所以vm.nextTick(callback)是在dom更新结束后执行的。

问:vue-router 实现原理?

答:vue-router最常用的模式有两种分别是hash模式和history模式:

hash 模式

hash模式是vue-router的默认路由模式,它的标志是在域名之后带有一个#,通过window.location.hash获取到当前urlhash。·hash·模式下通过hashchange方法可以监听urlhash的变化。hash模式的特点是兼容性更好,并且hash的变化会在浏览器的history中增加一条记录,可以实现浏览器的前进和后退功能。

history 模式

history模式是另一种前端路由模式,它基于HTML5history对象,通过location.pathname获取到当前 url 的路由地址。history模式下,通过pushStatereplaceState方法可以修改url地址,结合popstate方法监听url中路由的变化。

问:vuex 实现原理?

答:

Vuex在初始化时,在全局存储了Vue的实例,在install函数中,首先会判断是否已经调用了Vue.use(Vuex),然后调用applyMixin方法进行初始化的一些操作,applyMixin方法只做了一件事情,就是将所有的实例上挂载一个$store对象,在使用vuex的时候,会将store挂载在根组件之上。在第一次调用vuexInit函数时,options.store就是根选项的store,因此会判断其类型是不是function,若是则执行函数并将结果赋值给根实例的$store中,否则直接赋值。

Vuexstate状态是响应式,是借助vuedata是响应式,将state存入vue实例组件的data中,Vuexgetters则是借助vue的计算属性computed实现数据实时监听。

结束语

以上面试题是笔者在面试过程中面试官们问到的做了一些调研学习和整理,可能会有些许的错误,大家可以在下面评论指出错误,大家共同学习进步。如果有哪些方面没有覆盖到的地方大家也可以评论告诉我,后面回补齐。

标签:vue