1.v-show和v-if的区别
- v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。
- v-show 会被编译成指令,条件不满足时控制样式将对应节点隐藏 (display:none)
使用场景
- v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景
- v-show 适用于需要非常频繁切换条件的场景
2.为何在v-for中用key
必须用key,且不能是index和random
diff算法中通过tag和key来判断,是否是sameNode
减少渲染次数,提升渲染性能
如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快
相关代码如下
// 判断两个vnode的标签和key是否相同 如果相同 就可以认为是同一节点就地复用
function isSameVnode(oldVnode, newVnode) {
return oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key;
}
// 根据key来创建老的儿子的index映射表 类似 {'a':0,'b':1} 代表key为'a'的节点在第一个位置 key为'b'的节点在第二个位置
function makeIndexByKey(children) {
let map = {};
children.forEach((item, index) => {
map[item.key] = index;
});
return map;
}
// 生成的映射表
let map = makeIndexByKey(oldCh);
3.描述vue组件生命周期(父子组件)
单组件:
- beforeCreate 在实例初始化之后,数据观测observer 和event、watcher事件配置之前被调用
- created 实例已经创建完成,在这一步,以下配置被完成
- 数据观测
- 属性和方法的运算
- watch/event时间回调
- $el尚未生成
- beforeMount 在挂载之前被调用,render尚未被调用
- mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用
- beforeUpdate 数据更新时,被调用,发生在虚拟Dom重新渲染和打补丁之前
- update 由于数据更改导致的虚拟Dom重新渲染和打补丁,在这之后调用
- beforeDestroy 实例销毁之前调用
- destroyed 实例销毁之后调用,调用后Vue实例的所有东西都会被解绑,所有的事件监听会被移除,子实例被销毁,该钩子在服务端渲染期间不被调用
- keep-alive(activated & deactivated)
vue 的父组件和子组件生命周期钩子函数执行顺序?
- 加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted - 子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated - 父组件更新过程
父 beforeUpdate -> 父 updated - 销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
4.vue组件如何通讯(常见)
- props
- $emit
- $attr
- $listener
- provide inject (隔代通信)
- children
- vuex
5.描述组件渲染和更新的过程
组件渲染与更新
Vue 原理的三大模块分别为响应式、vdom 和模板编译,前面已经分别学习过,现在通过总结渲染过程来将它们串起来回顾。
初次渲染过程
- Step1:解析模板为 render 函数(这步操作或在开发中通过 vue-loader 已完成)
- Step2:触发响应式,监听 data 属性 getter 和 setter(下一步执行 render 函数可能会用到 getter)
- Step3:执行 render 函数,生成 vnode,渲染节点 patch(elem, vnode)
更新过程
- Step1:修改 data,触发 setter(前提是该 data 此前在 getter 中已被监听,即模板中被引用的 data)
- Step2:重新执行 render 函数,生成 newVnode
- Step3:更新节点 patch(vnode, newVnode)
其中 vnode 和 newVnode 的最小差异由 patch 的 diff 算法计算。
完整流程图
组件渲染与更新的完整流程图如下所示:
- 黄色方框为 render 函数(此时模板已经编译完),它会生成 vnode(绿色 Virtual DOM Tree)。
- 黄色方框在执行 render 时,会触发(Touch)紫色圆圈(Data)里面的 getter。
- 紫色圆圈(Data)里的 getter 触发时,会收集依赖,模板里哪个变量的 getter 被触发了,就会将相应变量观察起来(蓝色圆圈 Watcher)
- 一旦修改了 Data,就会通知 Watcher,如果修改的 data 是之前作为依赖被观察的,则重新触发渲染(re-render)。
6.双向数据绑定v-model的实现原理
简单的说,就是 :value 和 @input 的结合使用,v-model就是他们两个的语法糖。
input元素的value - this.name
绑定input事件 this.name = $event.target.value
data更新触发re-render
v-model 只是语法糖而已
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件 在普通标签上
<input v-model="sth" /> //这一行等于下一行
<input v-bind:value="sth" v-on:input="sth = $event.target.value" />
在组件上
<currency-input v-model="price"></currentcy-input>
<!--上行代码是下行的语法糖
<currency-input :value="price" @input="price = arguments[0]"></currency-input>
-->
<!-- 子组件定义 -->
Vue.component('currency-input', {
template: `
<span>
<input
ref="input"
:value="value"
@input="$emit('input', $event.target.value)"
>
</span>
`,
props: ['value'],
})
7.对MVVM的理解
映射关系简化,隐藏controller MVVM在MVC的基础上,把控制层隐藏掉了。
Vue不是一个MVVM框架,它是一个视图层框架。
ViewModal是一个桥梁,将数据和视图进行关联。
8.computed 和 watch 的区别和运用的场景
computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,它可以设置 getter 和 setter。
watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
计算属性一般用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑
9.为何组件data必须是一个函数
因为js本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data 作为一个函数返回一个对象,那么每一个实例的 data 属性都是独立的,不会相互影响了
10.ajax请求应该放在哪个生命周期
可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面 loading 时间;
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
作者:Big shark@LX
链接:juejin.cn/post/696122…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
11.如何将组件所有props传递给子组件
$props:<User v−bind="$props" />
12.如何自己实现v-model
13.多个组件有相同的逻辑,如何抽离?
mixin
以及mixin的一些缺点
mixin的缺点:
变量来源不明确,不利于阅读
多mixin可能会造成命名冲突
mixin和组件可能出现多对多的关系,复杂度较高
mixin和组件的顺序?
- 组件 的 data, methods 优先级高于mixin data, methods 优先级
- 生命周期函数,先执行 mixin 里面的,再执行组件里面的
- 组件的 自定义属性 优先级高于 mixin 自定义属性 优先级
14.何时要使用异步组件
加载大组件
路由异步加载
15.何时需要使用keep-alive
缓存组件,不需要重复渲染
如多个静态tab页的切换
优化性能
juejin.cn/post/704307…
16.何时需要使用beforeDestory
解绑自定义事件 event.$off
清除定时器
解绑自定义的DOM时间,如window scroll等
17.什么是作用域插槽
18.Vuex中action和mutation有何区别
juejin.cn/post/696122…
action中处理异步,mutation不可以
mutation做原子操作
action可以整合多个mutation
19.Vue-router常用的路由模式
hash默认
H5 history(需要服务端支持)
vuerouter的两种模式的区别
- vue-router中有三种模式,分别是hash、history、abstract
- abstract在不支持浏览器的API换景使用
- hash模式兼容性好,但是不美观,不利于SEO
- history美观,historyAPI+popState,但是刷新会出现404
作者:海明月
链接:juejin.cn/post/704307…
vue-router 中常用的路由模式实现原理
hash 模式
- location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
- 可以为 hash 的改变添加监听事件
window.addEventListener("hashchange", funcRef, false);
history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
特点:虽然美观,但是刷新会出现 404 需要后端进行配置
作者:Big shark@LX
链接:juejin.cn/post/696122…
20.如何配置Vue-router异步加载
21.请用vnode描述一个DOM结构
22.监听data变化的核心API是什么
Object.defineProperty
以及深度监听、监听数组
有何缺点
23.Vue如何监听数组变化
vue中对数组没有进行defineProperty,而是重写了数组的7个方法。 分别是:
- push
- shift
- pop
- splice
- unshift
- sort
- reverse
因为这些方法都会改变数组本身。
数组里的索引和长度是无法被监控的。
Object.defineProperty不能监听数组变化
重新定义原型,重写push pop等方法,实现监听
Proxy可以原生支持监听数组变化
24.请描述响应式原理----面试必问
数组和对象类型的值变化的时候,通过defineReactive方法,借助了defineProperty,将所有的属性添加了getter和setter。用户在取值和设置的时候,可以进行一些操作。
缺陷:只能监控最外层的属性,如果是多层的,就要进行全量递归。
get里面会做依赖搜集(dep[watcher, watcher])set里面会做数据更新(notify,通知watcher更新)
作者:海明月
25.diff算法的时间复杂度
O(n)
在O(n^3)基础上做了一些调整
26.简述diff算法过程
patch(elem,vnode)和patch(vnode,newVnode)
patchVnode和addVnodes和removeVnodes
updateChildren(key的重要性)
27.Vue为何是异步渲染,$nextTick何用
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染, 所以为了性能考虑。vue会在本轮数据更新后,再去异步更新视图。
异步渲染(以及合并data修改),以提高渲染性能
$nextTick在DOM更新完之后,触发回调
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
28.Vue常见性能优化方式
合理使用v-show和v-if
合理使用computed
v-for时价key,以及避免和v-if同时使用
自定义事件、DOM事件及时销毁
合理使用异步组件
合理使用keep-alive
data层级不要太深
使用vue-loader在开发环境做模板编译(预编译)
webpack层面的优化(后面会讲)
前端通用的性能优化,如图片懒加载
使用SSR
-
对象层级不要过深,否则性能就会差
-
不需要响应式的数据不要放到 data 中(可以用 Object.freeze() 冻结数据)
-
v-if 和 v-show 区分使用场景
-
computed 和 watch 区分使用场景
-
v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if
-
大数据列表和表格性能优化-虚拟列表/虚拟表格
-
防止内部泄漏,组件销毁后把全局变量和事件销毁
-
图片懒加载
-
路由懒加载
-
第三方插件的按需引入
-
适当采用 keep-alive 缓存组件
-
防抖、节流运用
-
服务端渲染 SSR or 预渲染