vue面试题
1.那你能讲一讲MVVM吗?
Model-View-ViewModel
缩写(是数据驱动模型)。Model层代表数据层(后端的api接口数据),View代表视图层,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层,并自动将数据渲染到页面中,视图变化的时候(dom发生一些事件)会通知viewModel层更新数据。2.简单说一下Vue2.x响应式数据原理
Vue 的响应式原理是核心是通过 ES5 的Object.defindeProperty 中的访问器属性中的 get 和 set 方法(进行数据监视和劫持),当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发render函数,渲染 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
缺陷;1.深度监听,需要递归到底,一次性计算量大,监听的是data中已有的属性
2.无法监听新增属性/删除属性(Vue.set Vue.delete)
3.无法原生监听数组,需要特殊处理
vue3用到了es6的proxy进行响应式数据处理
Proxy 能正确监听数组方法
Proxy 能正确监听对象新增删除属性
Proxy 只在 getter 时才进行对象下一层属性的劫持 性能优化
Proxy 兼容性不好
Vue3.x改用Proxy
替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
❝Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?
❞
(很简单啊)
判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive
方法做遍历, 这样就实现了深度观测。
❝监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
❞
我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。
3.再说一下vue2.x中如何监测数组变化
4.nextTick知道吗,实现原理是什么?
在下次 DOM 更新循环结束之后执行延迟回调。
Vue 是异步执行 DOM 更新的,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后(同步任务),
同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM 。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel 方法,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
场景:
1. swiper轮播图,发送ajax获取数据后,监视数据,dom还是没有跟新,需要用到nextTick这个api,等下次dom跟新后执行,更新dom
2.点击按钮显示原本以 v-show = false 隐藏起来的输入框,并获取焦点。
showsou(){ this.showit = true this.$nextTick(function () { // DOM 更新了 document.getElementById("keywords").focus() }) }
3.点击获取元素宽度。
<div id="app"> <p ref="myWidth" v-if="showMe">{{ message }}</p> <button @click="getMyWidth">获取p元素宽度</button> </div> getMyWidth() { this.showMe = true; //this.message = this.$refs.myWidth.offsetWidth; //报错 TypeError: this.$refs.myWidth is undefined this.$nextTick(()=>{ //dom元素更新后执行,此时能拿到p元素的属性 this.message = this.$refs.myWidth.offsetWidth; }) }
5.说一下Vue的生命周期
6.再说一下Computed和Watch
计算属性computed :
侦听属性watch:
7.说一下v-if和v-show的区别
v-if
不会渲染DOM元素,v-show
操作的是样式(display),切换当前DOM的显示和隐藏。频繁切换用v-show,提高性能8.组件中的data为什么是一个函数?
9.说一下v-model的原理
我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text 和 textarea 元素使用 value 属性和 input 事件;
- checkbox 和 radio 使用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'> 相当于 <input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示
<template> <div id="app"> {{username}} <br/> <my-input type="text" v-model="username"></my-input> </div> </template> <script> import myinput from './components/myinput' export default { name: 'App', components:{'my-input': myinput}, data(){ return { username:'' } } } </script>
myinput.vue:
<template> <div class="my-input"> <input type="text" @input="handleInput"/> </div> </template> <script> export default { name: "myinput", props:{ value:{ //获取父组件的数据value type:String, default:'' } }, methods:{ handleInput(e){ this.$emit('input',e.target.value) //触发父组件的input事件 } } } </script>
10.Vue事件绑定原理说一下
addEventListener
绑定给真实元素的,组件事件绑定是通过Vue自定义的$on
实现的。template
转化为render
函数的过程。会经历以下阶段:- 第一步是解析模板,将
模板字符串
转换成AST语法树, 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。 - 第二步是对
AST
进行静态节点标记,Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对
,对运行时的模板起到很大的优化作用。 - 第三步是 将优化后的AST树生成
render
函数代码字符串(代码生成器)
11.Vue2.x和Vue3.x渲染器的diff算法分别说一下
简单来说,diff算法有以下过程
- 同级比较,再比较子节点
- 调用patchVNode函数,先判断新旧vnode是否都有子节点,(如果新的children没有子节点,将旧的子节点移除)
- 调用updateChildren,比较都有子节点的情况(核心diff),
- 递归比较子节点
正常Diff两个树的时间复杂度是O(n^3)
,但实际情况下我们很少会进行跨层级的移动DOM
,所以Vue将Diff进行了优化,从O(n^3) -> O(n)
,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了双端比较
的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。
12.再说一下虚拟Dom以及key属性的作用
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因
。
虚拟 DOM 的实现原理主要包括以下 3 部分:
- 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
- diff 算法 — 比较两棵虚拟 DOM 树的差异;
- pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
「key的作用是尽可能的复用 DOM 元素。」
新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。
13.keep-alive了解吗
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,实现组件缓存,其有以下特性:
- 一般结合路由和动态组件一起使用,用于缓存组件;
- 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
- 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
14.Vue中组件生命周期调用顺序说一下
组件的调用顺序都是先父后子
,渲染完成的顺序是先子后父
。
组件的销毁操作是先父后子
,销毁完成的顺序是先子后父
。
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
15.Vue2.x组件通信有哪些方式?
-
父子组件通信
父->子
props
,子->父$on、$emit,或者slot插槽
获取父子组件实例
$parent、$children
Ref
获取实例的方式调用组件的属性或者方法Provide、inject
官方不推荐使用,但是写组件库时很常用 -
兄弟组件通信
Event Bus
实现跨组件通信Vue.prototype.$bus = new Vue
Vuex
-
跨级组件通信
Vuex
$attrs、$listeners
Provide、inject
16.SSR了解吗?
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
- 更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
- 更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
- 更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
- 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
17.你都做过哪些Vue的性能优化
编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用,在v-for父容器处理v-if
- 如果需要使用v-for给每项元素绑定事件时使用事件委派,冒泡原理
- SPA 页面采用keep-alive缓存组件
- 频繁切换组件,使用v-show代替v-if
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
SEO优化
- 预渲染
- 服务端渲染SSR
打包优化
- 压缩代码
- Tree Shaking树摇
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
18.hash路由和history路由实现原理说一下
hash模式
location.hash
的值实际就是URL中#
后面的东西,路由路径变化,跳转页面,但是不会刷新页面
history模式
19.Vue-Router
的两种模式主要依赖什么实现的
hash
主要依赖location.hash
来改动 URL,达到不刷新跳转的效果.每次hash
改变都会触发hashchange
事件(来响应路由的变化,比如页面的更换)history
主要利用了HTML5
的history
API 来实现,用pushState
和replaceState
来操作浏览历史记录栈
20.webpack 是什么?webpack 常见的优化手段有哪些;
webpack 是一个资源处理工具,它的出现节省了我们的人力和时间; 可以对资源打包,解析,区分开发模式等等,
常见的优化手段:
- 分离第三方库(依赖),比如引入
dll
- 引入多进程编译,比如
happypack
- 提取公共的依赖模块,比如
commonChunkPlugin
- 资源混淆和压缩:比如
UglifyJS
- 分离样式这些,减小
bundle chunk
的大小,比如ExtractTextPlugin
- GZIP 压缩,在打包的时候对资源对齐压缩,只要部署的服务器能解析即可..减少请求的大小
- 还有按需加载这些,一般主流的框架都有对应的模块懒加载方式.
- 至于
tree shaking
目前webpack3/4已经默认集成
21.Vuex
你怎么理解
1.vuex是什么
vuex是一个专为vue.js应用程序开发的状态管理模式,且数据并不具有持久化的特性(默认情况下刷新就重置所有状态);state:存储数据,存储状态;在根实例中注册了store 后,用 this.$store.state 来访问;对应vue里面的data;存放数据方式为响应式,vue组件从store中读取数据,
如数据发生变化,组件也会对应的更新。 getter:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。 mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。 action:包含任意异步操作,通过提交 mutation 间接更变状态。 module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。
2.2 对于vuex的数据传递流程,如下图所示:
当组件进行数据修改的时候我们需要调用dispatch来触发actions里面的方法。actions里面的每个方法中都会有一个commit方法,当方法执行的时候会通过commit来触发mutations里面的方法进行数据的修改。mutations里面的每个函数都会有一个state参数,这样就可以在mutations里面进行state的数据修改,当数据修改完毕后,会传导给页面。页面的数据也会发生改变。
23.在哪个生命周期内调用异步请求?
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面 loading 时间;
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
在什么阶段才能访问操作DOM?
在钩子函数 mounted 被调用时,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。vue 具体的生命周期示意图可以参见如下,理解了整个生命周期各个阶段的操作,关于生命周期相关的面试题就难不倒你了。
24.父组件可以监听到子组件的生命周期吗?
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue <Child @mounted="doSomething"/> // Child.vue mounted() { this.$emit("mounted"); }
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue <Child @hook:mounted="doSomething" ></Child> doSomething() { console.log('父组件监听到 mounted 钩子函数 ...'); }, // Child.vue mounted(){ console.log('子组件触发 mounted 钩子函数 ...'); }, // 以上输出顺序为: // 子组件触发 mounted 钩子函数 ... // 父组件监听到 mounted 钩子函数 ...
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。