首先,对于vue中的一些书写规范以及常见问题,建议大家优先阅读 vue风格指南;这里面有很多面试中的常见点。
下面来看常见的面试题:
源码位置:src/compiler/codegen/index.js
官方说法:
永远不要把 v-if 和 v-for 同时用在同一个元素上。
一般我们在两种常见的情况下会倾向于这样做:
- 为了过滤一个列表中的项目 (比如 v-for=“user in users” v-if=“user.isActive”)。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
- 为了避免渲染本应该被隐藏的列表 (比如 v-for=“user in users” v-if=“shouldShowUsers”)。这种情形下,请将 v-if 移动至容器元素上 (比如 ul、ol)。
// 1-1代码:
// 当v-if和v-for处于同一元素时,
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
// 以上代码运行结果如下js一样,每次循环进行if判断:
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})
总结:
1-1代码
。详情参阅 vue风格指南:避免-v-if-和-v-for-用在一起必要
源码位置:src\core\instance\state.js - initData() 函数每次执行都会返回全新data对象实例
总结:
源码位置:src\core\vdom\patch.js - updateChildren()
总结:
源码分析1:必要性,lifecycle.js - mountComponent()
组件中可能存在很多个data中的key使用。
源码分析2:执行方式,patch.js - patchVnode()
patchVnode是diff发生的地方。
源码分析3:高效性,patch.js - updateChildren()
diff算法整体策略:深度优先、同层比较。
回答总体思路:
组件化定义、优点、使用场景和注意事项等方面展开陈述,同时要强调vue中组件化的一些特点。
组件定义源码:src\core\global-api\assets.js
vue-loader会编译template为render函数,最终导出的依然是组件配置对象。
组件化优点 :lifecycle.js - mountComponent()
组件、Watcher、渲染函数和更新函数之间的关系
组件化实现:
构造函数:src\core\global-api\extend.js
实例化及挂载:src\core\vdom\patch.js - createElm()
总结:
vue官网定义:
- 渐进式JavaScript框架
- 易用、灵活、高效
路由懒加载。
keep-alive缓存页面。
使用v-show复用DOM。
v-for遍历同时避免使用v-if。
长列表性能优化:
事件的销毁:vue组件销毁时。会自动解绑它的全部指令以及事件监听器,但是仅限于组件本身的事件;使用beforeDestroy生命周期函数。
图片懒加载:图片过多,为了页面的加载速度,未出现在可视区域的图片先不做加载,滚动到可视区后再加载;参考:vue-lazyload。
第三方插件按需引入;像element-ui这样的第三方库可以按需引入避免体积过大。
无状态组件标记为函数式组件,给template标签添加属性functional。
子组件分割。
变量本地化。
通信方式使用场景可以分为三类:
1. 父子组件通信
2. 兄弟组件通信
3. 跨层组件通讯
通信方式:
props
父子$emit/$on(事件总线$bus)
全局vuex
全局$parent/$children
父子$attrs/$listeners
跨层provide/inject
跨层总结:
$emit
向父组件发送数据,父组件通过v-on/@
来触发接收数据方法。// 创建bus文件并引入
import bus from './bus'
// 监听sendData事件
mounted(){
bus.$on('sendData',(val)=>{this.msg = val})
}
// 触发sendData事件
methods:{
sendMsg(){
bus.$emit('sendData','传递的值')
}
}
...mapState
。...mapGetters
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
$parent / $children与 ref
$parent / $children
:访问父/子实例ref
:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果在子组件上使用,引用就指向组件实例<component-a ref="comA"></component-a>
// 获取组件component-a的实例
const comA = this.$refs.comA;
$attrs/$listeners
只做数据传递,不做中间处理
(1) $attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定(class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs"
传入内部组件。通常配合 interitAttrs 选项一起使用。
(2) $listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners"
传入内部组件。
简单来说,$attrs
与$listeners
是两个对象,$attrs
里存放的是父组件中绑定的非 Props 属性, $listeners
里存放的是父组件中绑定的非原生事件。
provide/inject 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
注:provide和inject主要为高阶插件/组件库提供用例,能在业务中熟练运用,可以达到事半功 倍的效果!
- new Vue({el:'#app'})
- 单文件组件中,template下的元素div,其实就是"树"状数据结构中的"根"。
- diff算法要求的,源码中,patch.js里patchVnode()。
总结:
(1) 实例化Vue时,我们会寻找一个带有id的元素当做挂载对象,这个元素就相当于“根节点”,如果有多个带id的元素,Vue如何确定应当挂载到哪个el上面?如下代码:
<body>
<div id='app'></div>
</body>
<script>
var vm = new Vue({
el:'#app'
})
</script>
(2) 组件模板中会要求template标签下有一个div元素,div元素就相当于“根”元素;
<template>
<div>
</div>
</template>
而template这个标签有三个特性:
dispaly:none
;一个单文件组件就是一个vue实例,如果template下有多个div,那么如何指定vue实例的根入口?为了让组件可以正常生成一个vue实例,这个div会自然处理成程序的入口,通过这个根节点,来递归遍历整个vue树下的节点,并处理为vdom,最后再渲染成真正的HTML,插入到正确的位置。
(3) 源码中diff算法要求每个单文件组件需要有一个唯一的根元素。
nextTick官方文档的解释,它可以在DOM更新完毕之后执行一个回调
Vue-深入响应式原理:异步更新队列
总结: