要保证自己的面试竞争力,必须掌握Vue原理,前端高级面试或者大厂面试中常考。本篇描述虚拟DOM、diff算法、响应式、模版编译、组件渲染等Vue原理常考的知识点和面试题。
一、组件化基础
1、如何理解MVVM
MVVM是Model-View-ViewModel的缩写,是一种前端架构模式。在Vue中,MVVM指的是:
- Model:数据模型,指的是数据的定义和管理。
- View:视图,指的是用户界面。
- ViewModel:视图模型,指的是Vue实例,它是连接视图和数据模型的桥梁。
在MVVM架构中,ViewModel负责将数据模型中的数据绑定到视图上,同时还负责将视图上的用户操作反映到数据模型中。这种双向绑定的机制使得开发者不需要手动操作DOM,只需要关注数据的变化即可,大大提高了开发效率。
在Vue中,使用指令(如v-model)将视图和数据模型进行绑定,当数据模型中的数据发生变化时,视图会自动更新,反之亦然。同时,Vue还提供了computed和watch等特性,让开发者能够更加方便地管理数据和视图之间的关系。
2、监听data变化的核心API是什么
Vue中监听data变化的核心API是Object.defineProperty方法。Vue在创建Vue实例时会将data对象中的每个属性都转换为getter/setter,这样当属性值发生变化时,Vue就能够监听到变化并且通知视图进行更新。
这种监听数据变化的方式被称为响应式系统。在Vue中,响应式系统是通过依赖追踪来实现的。当一个组件渲染时,它会跟踪所有被用到的属性,将这些属性与组件建立一个依赖关系。当这些属性发生变化时,Vue会通知组件重新渲染。
除了Object.defineProperty,Vue还使用了ES6的Proxy来实现响应式系统。但是由于Proxy的兼容性问题,目前在Vue中还是主要使用Object.defineProperty来实现响应式系统。
3、vue如何深度监听data变化
在Vue中,可以使用watch选项来监听data的变化,但是这只能深度监听到对象的第一层属性变化。如果需要深度监听对象的变化,可以使用deep选项。示例代码如下:
watch: {
obj: {
handler: function(newVal, oldVal) {
console.log('obj changed', newVal, oldVal)
},
deep: true
}
}
在上述代码中,obj是需要深度监听的对象,handler是当对象发生变化时执行的回调函数,deep选项设置为true表示需要深度监听对象的变化。当obj中的属性发生变化时,handler函数就会被触发。
4、如何监听数组变化
在Vue中,可以使用watch选项来监听数组的变化,但是这种方式只能监听到数组整体的变化,无法监听到具体元素的变化。如果需要监听数组元素的变化,可以使用Vue提供的$watch方法或者使用第三方插件vue-watcher。
使用$watch方法监听数组变化的示例代码如下:
data() {
return {
arr: [1, 2, 3]
}
},
created() {
this.$watch('arr', function(newVal, oldVal) {
console.log('arr changed', newVal, oldVal)
}, { deep: true })
}
在上述代码中,arr是需要监听的数组,$watch方法的第一个参数是需要监听的属性名,第二个参数是当属性发生变化时执行的回调函数,第三个参数是选项对象,其中deep选项设置为true表示需要深度监听对象的变化。
使用vue-watcher插件监听数组变化的示例代码如下:
import Watcher from 'vue-watcher'
data() {
return {
arr: [1, 2, 3]
}
},
created() {
new Watcher(this, 'arr', function(newVal, oldVal) {
console.log('arr changed', newVal, oldVal)
})
}
在上述代码中,arr是需要监听的数组,Watcher是vue-watcher插件提供的监听器类,通过创建一个新的Watcher实例并传入需要监听的对象和回调函数来监听数组变化。
5、虚拟DOM(Virtual DOM)updateChildren函数实现过程
Virtual DOM是一种用于构建用户界面的技术,它通过比较新旧虚拟DOM树来实现组件视图的更新。在比较新旧虚拟DOM树时,需要遍历两棵树,找出它们之间的差异点,然后只更新差异点所在的节点。这个过程被称为“diff算法”。
在Virtual DOM中,updateChildren函数是用来比较新旧虚拟DOM树中同一层级的子节点的。它的实现过程如下:
-
首先,遍历新旧虚拟DOM树中同一层级的子节点,找到它们之间的差异点。
-
如果新旧虚拟DOM树中同一位置的子节点类型不同,直接用新节点替换旧节点。
-
如果新旧虚拟DOM树中同一位置的子节点类型相同,但是它们的key不同,也直接用新节点替换旧节点。
-
如果新旧虚拟DOM树中同一位置的子节点类型相同,并且它们的key相同,那么需要比较它们的属性和子节点是否有变化。
-
如果属性有变化,更新属性。
-
如果子节点有变化,递归调用updateChildren函数进行比较。
-
如果新虚拟DOM树中的子节点比旧虚拟DOM树中的子节点多,那么在旧虚拟DOM树中添加新节点。
-
如果新虚拟DOM树中的子节点比旧虚拟DOM树中的子节点少,那么在旧虚拟DOM树中删除多余的节点。
-
最后,返回更新后的虚拟DOM节点。
需要注意的是,由于Virtual DOM的实现需要进行大量的计算,因此在性能方面可能存在一定的问题。为了解决这个问题,可以采用一些优化策略,例如使用key优化diff算法、使用shouldComponentUpdate函数减少不必要的更新、使用Immutable.js等不可变数据结构等。
6、vdom的核心概念是什么
vdom的核心概念是将页面抽象成一个虚拟的DOM树,并通过比较新旧虚拟DOM树的差异,最小化页面的重新渲染,从而提高页面的性能和响应速度。
具体来说,vdom的核心概念包括以下几个方面:
-
虚拟DOM树:将页面抽象成一个虚拟的DOM树,通过JavaScript对象来表示,虚拟DOM树中每一个节点,通常由h函数创建,它接收三个参数:第一个参数是要创建的DOM节点的标签名,第二个参数是该节点的属性,第三个参数是该节点的子节点,h函数返回的是一个vnode对象,可以用于比较新旧虚拟DOM树的差异。
-
diff算法:比较新旧虚拟DOM树的差异,找出需要更新的节点,最小化页面的重新渲染。
-
patch操作:根据diff算法找出需要更新的节点后,将更新的操作应用到实际的DOM树上,从而实现页面的更新。
-
性能优化:通过使用vdom可以减少页面的重新渲染,提高页面的性能和响应速度。
总之,vdom的核心概念是将页面抽象成一个虚拟的DOM树,并通过比较新旧虚拟DOM树的差异,最小化页面的重新渲染,从而提高页面的性能和响应速度。
7、vdom存在的价值是什么
-
提高页面渲染性能。使用vdom可以减少DOM操作,避免频繁的重绘和回流,从而提高页面渲染性能。
-
提高开发效率。使用vdom可以将页面的状态和UI分离,使得开发者可以更加专注于业务逻辑的实现,从而提高开发效率。
-
跨平台应用。vdom可以在不同的平台上实现一致的UI渲染,从而提高跨平台应用的开发效率和一致性。
-
支持组件化开发。vdom可以将UI组件化,使得组件的复用和维护更加方便,从而提高开发效率和代码复用性。
-
支持前端框架。vdom是很多前端框架(如React、Vue等)的核心技术之一,使用vdom可以方便地实现前端框架的功能,如数据绑定、事件处理等。
8、模版编译是什么
在Vue中,模板编译是将Vue模板转换为渲染函数(render)的过程。Vue模板是一种基于HTML的语法扩展,它允许开发者使用类似于HTML的语法来描述数据的展示方式,而无需手动操作DOM。在模板编译过程中,Vue会将模板转换为渲染函数(render),这个函数可以接受数据作为参数,并返回一个VNode节点树,最终渲染成真实的DOM元素。
模板编译的过程可以分为以下几个步骤:
-
解析模板:将模板解析为抽象语法树(AST)。
-
优化模板:对AST进行静态节点标记和静态根节点提升等优化。
-
生成代码:根据AST生成可执行的渲染函数。
在Vue中,模板编译是在运行时进行的。当组件第一次被渲染时,Vue会将组件的模板编译成渲染函数,并缓存起来以供后续使用。这样可以避免重复的模板编译,提高了页面的性能和响应速度。
9、组件渲染/更新过程
(1)初次渲染函数
- 解析模版为render函数(或在开发环境已完成,用vue-loader)
- 触发响应式,监听data属性,getter setter
- 执行render函数,生成vnode,patch(element,vnode)
(2)更新过程
- 修改data,触发setter(此前在getter中已被监听)
- 重新执行render函数,生成newVnode
- ptach(vnode,newVnode)
(3)异步渲染
在更新组件时,Vue 会将更新的任务放到一个异步队列中,等到当前任务执行完毕后再执行更新任务,这样可以避免频繁的页面重绘,提高页面性能和响应速度。
以下是完成流程图
10、如何用JS实现hash路由
实现hash路由的主要思路是通过监听URL中hash值的变化,来实现前端路由的跳转。具体实现可以参考以下步骤:
-
使用
window.location.hash获取当前页面的hash值。 -
使用
window.addEventListener('hashchange', function(){})方法监听hash值的变化。 -
在监听函数中,可以通过
window.location.hash获取到新的hash值,并根据需要进行页面跳转。
以下是一份简单的示例代码:
// 监听hash值变化
window.addEventListener('hashchange', function() {
// 获取新的hash值
var hash = window.location.hash;
// 根据需要进行页面跳转
if(hash === '#home') {
// 跳转到首页
} else if(hash === '#about') {
// 跳转到关于页面
}
});
// 设置新的hash值
window.location.hash = '#home';
11、如何用JS实现H5 history路由
实现H5 history路由的主要思路是通过监听浏览器的历史记录变化,来实现前端路由的跳转。具体实现可以参考以下步骤:
-
使用
window.history.pushState()方法添加新的历史记录,并传入需要保存的数据和页面标题等参数。 -
使用
window.addEventListener('popstate', function(event){})方法监听浏览器历史记录的变化。 -
在监听函数中,可以通过
event.state获取到历史记录中保存的数据,并根据需要进行页面跳转。
以下是一份简单的示例代码:
// 添加新的历史记录
window.history.pushState({page: 'home'}, '', '/home');
// 监听历史记录变化
window.addEventListener('popstate', function(event) {
// 获取历史记录中保存的数据
var state = event.state;
// 根据需要进行页面跳转
if(state.page === 'home') {
// 跳转到首页
} else if(state.page === 'about') {
// 跳转到关于页面
}
});