vue面试题

122 阅读10分钟

vue生命周期

什么是Vue生命周期?

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期

Vue生命周期的作用是什么?

它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑

Vue生命周期总共有几个阶段?

它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后

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

第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子

DOM渲染在哪个周期中就已经完成?

DOM 渲染在 mounted 中就已经完成了

每个生命周期适合哪些场景?

生命周期钩子的一些使用方法:

beforecreate : 可以在这加个loading事件,在加载实例时触发

created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用

mounted : 挂载元素,获取到DOM节点

updated : 如果对数据统一处理,在这里写上相应函数

beforeDestroy : 可以做一个确认停止事件的确认框

nextTick : 更新数据后立即操作dom

    • beforeCreate阶段:vue实例的挂载元素el和数据对象data都是undefined,还没有初始化。
    • created阶段:vue实例的数据对象data有了,可以访问里面的数据和方法,未挂载到DOM,el还没有
    • beforeMount阶段:vue实例的el和data都初始化了,但是挂载之前为虚拟的dom节点
    • mounted阶段:vue实例挂载到真实DOM上,就可以通过DOM获取DOM节点
    • beforeUpdate阶段:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
    • updated阶段:虚拟DOM重新渲染和打补丁之后调用,组成新的DOM已经更新,避免在这个钩子函数中操作数据,防止死循环
    • beforeDestroy阶段:实例销毁前调用,实例还可以用,this能获取到实例,常用于销毁定时器,解绑事件
    • destroyed阶段:实例销毁后调用,调用后所有事件监听器会被移除,所有的子实例都会被销毁

Vue 组件间通信有哪几种方式?

(1)props / $emit 适用 父子组件通

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2)ref 与 $parent / $children 适用 父子组件通信

    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

    • $parent / $children:访问父 / 子实例

(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件

(4)$attrs/$listeners 适用于 隔代组件通信

    • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。

    • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

(5)provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

请介绍一下你对vuex的理解?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

    • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
    • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
    • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
    • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
    • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

Vue的双向数据绑定原理是什么

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 具体实现步骤,感兴趣的可以看看:

  1. 当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  4. MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果

检测数据类型

Object.prototype.toString.call():较为准确的判断方法。toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型。

v-model 的原理

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

    • text 和 textarea 元素使用 value 属性和 input 事件;
    • checkbox 和 radio 使用 checked 属性和 change 事件;
    • select 字段将 value 作为 prop 并将 change 作为事件。

Vue 组件 data 为什么必须是函数 ?

因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。

所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。

谈谈你对 keep-alive 的了解?

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

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

请介绍一下你对vuex的理解?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

    • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
    • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
    • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
    • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
    • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

请介绍一下你对vue-router的理解?

vue-router 实现原理? (课程中的代码)

**
**

vue-router 有 3 种路由模式:hash、history、abstract,

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;

  • history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;

  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

1)hash 模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':

hash 路由模式的实现主要是基于下面几个特性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用  JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的实现主要基于存在下面几个特性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;

  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);

  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

导航钩子函数(导航守卫)?

  • 全局守卫
  1. router.beforeEach 全局前置守卫 进入路由之前

  2. router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用

  3. router.afterEach 全局后置钩子 进入路由之后

// main.js 入口文件
    import router from './router'; // 引入路由
    router.beforeEach((to, from, next) => { 
      next();
    });
    router.beforeResolve((to, from, next) => {
      next();
    });
    router.afterEach((to, from) => {
      console.log('afterEach 全局后置钩子');
    });
  • 路由独享的守卫 你可以在路由配置上直接定义 beforeEnter 守卫

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
  • 组件内的守卫 你可以在路由组件内直接定义以下路由导航守卫

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用,我们用它来禁止用户离开
    // 可以访问组件实例 `this`
    // 比如还未保存草稿,或者在用户离开前,setInterval销毁,防止离开之后,定时器还在调用。
  }
}

.ref的作用

    1. 获取dom元素this.$refs.box
    2. 获取子组件中的datathis.$refs.box.msg
    3. 调用子组件中的方法this.$refs.box.open()    

Vue 中的 key 有什么作用?

Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,

谈谈你对 keep-alive 的了解?

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

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

computed 和 watch 的区别和运用的场景?

computed:  是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch:  更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:

    • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

    • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

computed的原理

computed 本质是一个惰性求值的观察者。

computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。

其内部通过 this.dirty 属性标记计算属性是否需要重新求值。

当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变        化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。 )

没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他        地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。 )

说说你对 SPA 单页面的理解,它的优缺点分别是什么?

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

    • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
    • 基于上面一点,SPA 相对对服务器压力小;
    • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

    • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
    • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
    • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

开发中常用的指令有哪些

  • v-model :一般用在表达输入,很轻松的实现表单控件和数据的双向绑定
  • v-html: 更新元素的 innerHTML
  • v-showv-if: 条件渲染, 注意二者区别

使用了v-if的时候,如果值为false,那么页面将不会有这个html标签生成。 v-show则是不管值为true还是false,html元素都会存在,只是CSS中的display显示或隐藏

  • v-on : click: 可以简写为@click,@绑定一个事件。如果事件触发了,就可以指定事件的处理函数
  • v-for:基于源数据多次渲染元素或模板块
  • v-bind: 当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM

语法:v-bind:title="msg" 简写::title="msg"

vuex页面刷新数据丢失问题的四种解决方式

为什么说刷新页面vuex的数据会丢失

刷新页面vuex的数据会丢失属于正常现象,因为JS的数据都是保存在浏览器的堆栈内存里面的,刷新浏览器页面,以前堆栈申请的内存被释放,这就是浏览器的运行机制,那么堆栈里的数据自然就清空了。

第一种方法用sessionStorage

将接口返回的数据保存在vuex的store里,也将这些信息也保存在sessionStorage里。注意的是vuex中的变量是响应式的,而sessionStorage不是,当你改变vuex中的状态,组件会检测到改变,而sessionStorage就不会了,页面要重新刷新才可以看到改变,所以应让vuex中的状态从sessionStorage中得到,这样组件就可以响应式的变化。 `const state = { authInfo: JSON.parse(sessionStorage.getItem("COMPANY_AUTH_INFO")) || {} }

const getters = { authInfo: state => state.authInfo, } const mutations = { SET_COMPANY_AUTH_INFO(state, data) { state.authInfo = data sessionStorage.setItem("COMPANY_AUTH_INFO", JSON.stringify(data)) } }

//actions 模块里无需使用 sessionStorage

export default { namespaced: true, state, getters, mutations, //actions, } ` 其实这里还可以用 localStorage,但是它没有期限;所以常用的还是sessionStorage,当浏览器关闭时会话结束。

第二种方法是 vuex-along

示例如下
(1)安装 vuex-along

npm install vuex-along --save

(2)在store文件夹里的index.js中引入vuex-along并配置相关代码 `import Vue from 'vue' import Vuex from 'vuex' import indexOne from "./modules/indexOne" import VueXAlong from 'vuex-along'

Vue.use(Vuex) const store=new Vuex.Store({ strict: false, modules:{ indexOne },

plugins: [VueXAlong({ name: 'along', //存放在localStroage或者sessionStroage 中的名字 local: false, //是否存放在local中 false 不存放 如果存放按照下面session的配置配 session: { list: [], isFilter: true }
//如果值不为false 那么可以传递对象 其中 当isFilter设置为true时, list 数组中的值就会被过滤调,这些值不会存放在seesion或者local中 })]

})

export default store; `

第三种方法是 vuex-persistedstate

示例如下
(1)安装 vuex-persistedstate

npm install --save vuex-persistedstate

(2)在store文件夹里的index.js中引入vuex-persistedstate并配置相关代码 `import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import createPersistedState from "vuex-persistedstate" Vue.use(Vuex)

const store = new Vuex.Store({ modules: { user },

plugins: [createPersistedState({ storage: window.sessionStorage, reducer(val) { return { // 只储存state中的user user: val.base } } })] })

export default store; `

第四种方法是 vuex-persist

示例如下
(1)安装 vuex-persist

npm install --save vuex-persist
or
yarn add vuex-persist

(2)在store文件夹里的index.js中引入vuex-persist并配置相关代码 `import Vue from 'vue' import Vuex from 'vuex' import indexOne from "./modules/indexOne" import VuexPersistence from 'vuex-persist' Vue.use(Vuex)

const vuexLocal = new VuexPersistence({ storage: window.localStorage })

const store = new Vuex.Store({ strict: false, modules:{ indexOne, }, plugins: [vuexLocal.plugin]

})

export default store; `

项目部署的完整流程

路由传参的几种方式

传参方式可划分为params 传参 和query传参,而params传参可以分为在url地址栏当中显示参数和不显示参数俩种方式

1、query 传参(显示参数)也可分为声明式和编程式 两种方式

声明式router-link:该方式是通过 router-link 组件的 to 属性实现,不过使用该方式传值的时候,需要子路由提前配置好路由别名

编程式 this.$router.push:使用该方式传值的时候,同样需要子路由提前配置好路由别名(name 属性)

2.params 传参(显示参数)又可分为 声明式 和 编程式 两种方式

2.1 声明式router-link:该方式是通过router-link组件的to属性实现,子路由需要提前配置好参数 2.2编程式 this.$router.push:同样需要子路由提前配置好参数。

3、params传参(不显示参数)也可分为声明式和编程式两种方式,与显示参数不同的是,这里是通过路由的别名 name 进行传值的

(只能通过name 不能通过path )

项目部署

前端本地存储方法

权限管理

封装组件