vue常见面试题

1、真实的DOM及其渲染过程

先用html分析器分析html代码,生成一个dom树。再用css分析器,分析css样式及行内样式,生成样式表,将样式表和dom树关联起来,创建一个Render树。最后浏览器开始布局绘制树 。

Render树是DOM树和CSSOM树构建完毕才开始构建的吗?

这三个过程在实际进行的时候又不是完全独立,而是会有交叉。会造成一边加载,一遍解析,一遍渲染的工作现象

操作DOM的代价仍旧是昂贵的,为了节省性能,提升用户体验所以创建了虚拟dom

2.什么是虚拟dom

用js描述真实的dom树,状态的变化首先作用于虚拟 DOM,最终映射到真实的DOM中

3.vue的diff算法

当改变数据层,会修改虚拟dom,diif算法将新的虚拟dom和旧的虚拟dom进行对比,发现不同的地方就直接修改真实dom。diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的dom打补丁

在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。

diff算法的实现

diff算法的比较

其中sameVnode比较部分函数主要代码如下

sameVnode代码

如果两个节点不一样了就会直接替换真实的dom,不一样则继续比较其子元素。

patchVnode实现过程
比较函数的实现过程

该函数主要做了以下几个操作:
找到对应的真实dom,称为el

判断Vnode和oldVnode是否指向同一个对象,如果是,那么直接return

如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点。

如果oldVnode有子节点而Vnode没有,则删除el的子节点

如果oldVnode没有子节点而Vnode有,则将Vnode的子节点真实化之后添加到el

如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要

Vue 的 updateChildren函数可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。

4.key值的作用

v-for中为什么要设置key

如图上sameVnode函数中,会先用key值去判断两个元素是否相等,如果不设置key值的话它默认为undefined,此时会认为它是两个相同的节点,然后就会一直下去走patchVnode。所以v-for中设置key高效的更新虚拟dom。

vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

为什么不要用index作为key?

当用index作为key的时候,删除某个节点它后面的所有节点的index都会发生变化,会导致其他都重新渲染。

5.vue组件中的data为什么必须是函数

因为组件是可以被复用的,所以注册一个组件实质上就是创建了一个组件构造器的引用,当我们真正使用组件的时候才会将其实例化。而data其实就相当于原型上的一个属性,如果将它设置成对象的话,在用的时候给它重新赋值会直接修改原型的值。但是创建成一个函数它就有自己的作用域了,就不会互相影响了。

6.vuex

7.vue如何实现双向数据绑定的

vue采用的是数据劫持+发布者订阅的方式,通过object.defineProperty()来劫持数据的getter和setter,当数据发生变化的时候,告诉订阅者,并改变视图层

实现的大体思路:先创建一个监听器observer,监听所有属性,如果属性发生变化了,告诉订阅者。因为订阅者可能是多个,所以我们需要创建一个消息订阅器Dep,专门收集这些订阅者。

接着我们需要创建一个指令解析器compile,compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,更新视图。

流程图

https://www.yuque.com/yangmiao-8mvex/gs07o5/su777r

8.vue的生命周期

beforeCreated(创建之前)

实例还没有完全被创建出来,vue实例的挂载元素$el和data都为undefined,还未初始化。

created(创建后)

数据对象已经存在,可以调用methods中的方法操作data中的数据,但dom未生成,$el不存在。

beforeMount(挂载前)

vue 实例的 $el 和 data 都已初始化,挂载之前为虚拟的 dom节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中。

mounted(挂载后)

vue 实例挂载完成,data.message 成功渲染。内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了。实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,DOM 渲染在 mounted 中就已经完成了。

beforeUpdate(更新前)

当 data 变化时,会触发beforeUpdate方法 。data 数据尚未和最新的数据保持同步。

updated(更新后)

当 data 变化时,会触发 updated 方法。页面和 data 数据已经保持同步了。

beforeDestory(销毁前)

组件销毁之前调用 ,在这一步,实例仍然完全可用

destoryed(销毁后)

组件销毁之后调用,对 data 的改变不会再触发周期函数,vue 实例已解除事件监听和 dom绑定,但 dom 结构依然存在。

9.vue首屏加载慢的解决方法

导致加载慢的可能因素有如下:

网络延时问题

资源文件体积是否过大

资源是否重复发送请求去加载了

加载脚本的时候,渲染内容堵塞了

解决方案:

1、减少入口文件体积

常用的手段是路由懒加载,即将路由对应的组件打包成一个个的js代码块,当这个路由被访问到的时候才去加载。

路由懒加载实现方式有以下三种:

使用vue的异步加载技术,require()

使用ES6的import

webpack提供的require.ensure()实现懒加载

(import 是解构过程并且是编译时执行,require的性能相对于import稍低,因为require是在运行时才引入模块并且还赋值给某个变量)

2、静态资源本地缓存

后端返回资源问题:

采用HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头

采用Service Worker离线缓存

前端合理利用localStorage

3、UI框架按需加载

4、组件重复打包

在webpack的config文件中,修改CommonsChunkPlugin的配置,在webpack的config文件中,修改CommonsChunkPlugin的配置

5.图片资源的压缩

6、使用SSR,

也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器,vue可以使用nuxt.js来实现服务端渲染。

10.vue刷新页面store中的数据会丢失吗

会,刷新页面vue实例重新加载,从而sotre中的数据也被重置了。store是用来存储组件状态的,而不是做本地数据存储的。解决方案是可以将数据存储到sessionStorage

具体做法是在App.vue的created()周期函数中,通过监听beforeunload事件来进行数据的localStorage存储,beforeunload事件在页面刷新时进行触发。

if (window.localStorage.getItem("list") ) {

        this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(window.localStorage.getItem("list"))))

    }

window.addEventListener("beforeunload",()=>{

        window.localStorage.setItem("list",JSON.stringify(this.$store.state))

    })

11、使用 Object.defineProperty() 来进行数据劫持有什么缺点?

对一些属性进行操作时,使用这种方法无法拦截,比如给对象新增属性就无法触发组件的重新渲染。所以vue3.0是通过Proxy对对象进行代理,从而实现数据劫持。


12. Computed 和 Watch 的区别

computed支持缓存,只有依赖的数据发生变化才会重新计算,不支持异步

watch不支持缓存,数据变化就会触发相应的操作,支持异步,

13.slot是什么,有什么作用,原理是什么

slot又名插槽,分为三类,匿名插槽,具名插槽和作用域插槽。

实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

14.过滤器的作用,如何实现一个过滤器

filters不会修改数据,而是过滤数据,改变用户看到的输出(计算属性 computed ,方法 methods 都是通过修改数据来处理数据格式的输出显示)

过滤器

15. 常见的事件修饰符及其作用

.stop:防止事件冒泡

,prevent:阻止浏览器的默认事件(1.超链接的自动跳转  2.form标签中的submit按钮点击导致的页面刷新  3.网页中右击单机,会弹出一个菜单)

.capture:与事件冒泡方法相反,事件捕获由外到内

.once:只会触发一次

16.v-model 是如何实现的,语法糖实际是什么?

(1)作用在表单元素上

动态绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动态把 message设置为目标值:

input的v-model源码

(2)作用在组件上

在自定义组件中,v-model 默认会利用名为 value 的 prop和名为 input 的事件。本质是一个父子组件通信的语法糖,通过prop和$.emit实现

在自己组件内使用:

组件源码

17.keep-alive原理及用法

在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中

在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件,则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。

生命周期函数:

activated:在 keep-alive 组件激活时调用

该钩子函数在服务器端渲染期间不被调用

 deactivated:在 keep-alive 组件停用时调用

18. $nextTick 原理及作用

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容