Vue面试

161 阅读4分钟

v-if和v-for哪个优先级高?如果同时出现怎么优化提高性能

同级情况下

// compiler/codegen/index.js
if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
<ul>
	<li v-for="item in list" v-if="show">{{item.title}}</li>
</ul>
<script>
const app = new Vue({})
console.log(app.$options.render)
</script>
to
(function anonymous(
) {
with(this){
	return _c('div',{attrs:{"id":"app"}},[_c('p',[_v("v-for/v-if优先级?如何正确使用避免性能问题")]),_v(" "),_c('ul',
    _l((list),function(item){return (isShow)?_c('li',{key:item.id},[_v(_s(item.title))]):_e()}),0)])}
})

不同级

<template v-if="isShow">
	<li v-for="item in list" :key="item.id">{{item.title}}</li>
</template>
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[(isShow)?_c('div',[_c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)]):_e()])}
})

结论

* v-for优先级高于v-if被解析
* 如果同时出现,每次渲染都会先执行循环在判断条件,无论如何循环都不可避免,浪费性能
* 要避免这种情况,则在外层嵌套template,这一层进行if判断,内部进行v-for循环
* <li v-for="item in list" v-if="item.isShow">该如何优化呢?</li>:用计算属性做过滤后再循环

Vue组件data为什么必须是个函数,而Vue的跟实例没有此限制

core/instance/state.js initData() 函数每次执行都会返回全新data对象实例

// core/instance/state.js
let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
//index.html
vue.component('comp', {
	template: '<div>{{count}}}</div>',
    data:{count: 0}
})
// 多个comp组件初始化时使用的data数据都共享一个了,

结论

Vue组件可能存在多个实例,如果使用对象形式定义data,会导致他们共用一个data对象,那么状态变更将会影响所有组件实例,采用函数形式定义,在initData时将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题,而在vue根实例创建过程中不存在该限制,也是因为根实例只能有一个

key的作用和工作原理

src/core/vdom/patch.js-updateChildren()

<div id="demo">
	<p v-for="item in items" :key="item">{{item}}</p>
</div>

// js
new Vue({
	el:'#demo',
    data:{
    	items:['a','b','c','d','e']
    },
    mounted(){
    	settimeout(() => {
        	this.items.splice(2,0,'f')
        },2000)
    }
})
不使用Key:
更新5次,也发生了5次操作,执行1次添加
使用key:
更新5次,但是没有发生操作(一直在更新五个完全相同的节点,path时不会发生任何变更),执行1次添加

结论

  • key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少dom操作量,提高性能
  • 若不设置key还可能在列表更新时引发一些隐藏的bug
  • vue中在使用相同标签名元素的过度切换时,也会使用到key属性,其目的也是为了让vue可以却分他们,否则vue只会替换其内部属性而不会触发过度效果

vue中的diff算法

vue中一个组件一个watcher,组件总可能存在很多个data中的key使用

  • diff算法是虚拟dom技术的必然产物,通过新旧虚拟DOM最对比,将变化的地方更新在真实DOM上;另外也需要diff高效的执行对比过程,从而降低时间复杂度
  • vue2.x中为了降低watcher粒度,每个组件只有一个watcher与之相对应,只有引入diff才能找到发生变化的地方
  • vue中diff执行的时刻是组件实例执行更新函数,它会对上一个渲染结果oldNode和新的渲染结果newVnode,此过程为patch。
  • diff过程整体遵循深度优先,同层比较,两个节点之间比较会根据他们是否拥有子节点或者文本节点做不同操作,比较两组节点的是算法重点,首先假设头尾节点可能相同做4次对比尝试,如果没有找到相同节点,才按通用方式遍历查找,查找结束在按情况处理剩下的节点,借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效

vue组件化的理解

组件化定义/优点/使用场景/和注意事项/vue中组件化的一些特点

 用户在程序中一些独立的功能模块提炼出来,切分为更小的块,这些块有独立的逻辑,更好的复用性。

组件定义-

Vue.component('comp',{
	template:'<div>this is a component</div>
})
// vue-loader会编译template为render函数,最终导出的依然是组件配置对象

组件优缺点(组件/watcher/渲染函数/更新函数之间的关系)

组件和watcher是一一对应的关系(mountComponent),每次发生变更只会调用当前实例内的update和render
数据变化多的地方可以提取为单独组件

组件化实现

  • 构造函数 src\core\global-api\extend.js
  • 实例化及挂在 src\core\vdom\patch.js (createEle)

总结

  • 组件是独立和可复用的代码组织单元。组件系统是vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用
  • 组件化开发能大幅度提高应用开发效率/测试行/复用性
  • 组件使用按分类:页面组件/业务组件/通用组件
  • vue的组件时基于配置的,我们通常编写的组件是组件的配置而非组件,框架后续会生成其构造函数,基于VueComponent扩展Vue
  • vue中常见组件化技术有:属性prop/自定义事件/插槽等,用于组件通信、扩展等
  • 合理划分组件,有助于提升应用性能
  • 组件应该是高内聚低耦合
  • 遵循单项数据流的原则

vue设计原则的理解

渐进式框架

与其他大型框架不同的是,vue被设计为可以自底向上逐层应用。vue的核心库只关注视图层,不仅易上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时。vue也完全能够为复杂的单页应用提供驱动

易用性

vue提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用的核心特性即可完成;随着应用规模不断扩大,我们才可以逐渐引入路由、状态管理、vue-cli等库和工具,不管是应用体积还是学习难度都是一个逐渐增加的平和曲线

高效性

超快的虚拟DOM和diff算法使我们的应用拥有最佳性能表现。追求高效的过程还在继续,vue3引入proxy对数据响应式改进以及编译器对于静态内容编译的改进都会让vue更加高效

为什么vue要求组件模板只能有一个跟元素

new Vue({el: '#app'})

单文件组件中,template下的元素div,就是树状结构中的根

template标签特性

  • 隐藏性:改标签不会显示在页面的任何地方,即便里面有多少内容,永远是隐藏转态的,设置了display:none
  • 任意性:该标签可以写在任何地方,甚至是head、body、script内
  • 无效性: 该标签里的任何html内容都是无效的,不起任何作用,只能innerhtml获取到里面的内容 一个vue单文件就是一个vue实例,如果template下有多个div那么如指定vue实例的根入口呢,为了让组件可以正常生成一个vue实例,这个div会自然的处理成程序的入口,通过这个根节点,来递归遍历整个vue树下的所有节点,并处理为vdom

diff算法

mvc/mvp/mvvm 架构模式的理解

vue组件之间的通信

不同的场景选择不同的方式?(父子、兄弟、隔代) 1、props 2、emit/emit/on(事件总线) 3、vuex 5、provide/inject 4、parent/parent/children

vue性能优化方法

探讨代码层优化

  • 路由懒加载
const router = new vueRouter({
	routes: [
    	{path: '/home', component: () => import('tpl')}
    ]
})
  • keep-alive缓存页面
<template>
	<div>
    	<keep-alive>
        	<router-view/>
        </keep-alive>
    </div>
</template>
  • v-show服用DOM(视情况使用v-if/v-show)
  • v-for遍历避免使用v-if
  • 长列表性能优化
如果是纯粹的数据展示,不会有任何改变,就不需要做响应化
{
	data: () => ({users: []})
},
async created(){
	const list = await axios.get()
    this.users = object.freeze(list)
}
如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容(可视区域)
<recycle-scroller
	class="items"
    :items="items"
    :item-size="24"
></recycle-scroller>
// vue-virtual-scroller、vue-virtual-scroll-list
  • 事件销毁 vue组件销毁时,会自动解绑它的全部指令以及事件监听器,但是仅限于组件本身的事件 定时器、

  • 图片懒加载

vue-lazyload

  • 第三方插件按需引入 例如ele

  • 无状态组件标记为函数式组件 functional 没有组件实例,运行时耗费资源较小

<template function></template>
  • 子组件分割
<template>
	<div>
    	<childComp />
    </div>
</template>
<script>
export default {
	components: {
    	methods:{
        	heavy(){耗时任务}
        },
        render(h){
        	retyrb h('div',this.heavy())
        }
    }
}
</script>
  • 变量本地化 const counter

  • ssr服务端渲染(seo、首屏)

vue如果想扩展某个现有组件怎么做

1、使用Vue.mixin()全局混入

混入是一种分发vue组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项

<template>
<div id="app">
	<p>{{num}}</p>
    <button @click='add'></button>
</div>
</template>
<script>
	let addLog = {
    	updated: function(){
        	console.log('数据变化')
        }
    }
    export default {
    	name: 'app',
        data(){
        	return {
            	num: 1
            }
        },
        methods:{
        	add(){}
        },
        mixins:[addLog]
    }
</script>

mixins的调用顺序: 从执行的先后顺序来说,混入对象的钩子将在组件自身钩子之前调用,如果遇到全局混入,全局混入的执行顺序要前于混入和组件里的方法

加slot扩展

默认插槽和匿名插槽: slot

watch和computed的区别以及怎么选用

定义/语义区别

// watch 监听器
new Vue({
	data: {
    	foo: 1
    },
    watch: {
    	foo: function(newVal, oldVal){...}
    }
})
// computed
new Vue({
	data: {
    	foo: 1
    },
    computed:{
    	laia(){return ....}
    }
})

功能区别

watch更通用,computed派生功能都能实现,计算属性底层来自于watch,单做了更多,例如缓存

用法区别

computed更简单/更高效,优先使用,有些必须watch,比如值变化要和后端交互

使用场景

watch:需要在数据变化时执行异步操作或开销较大的操作时使用,简单讲,当一条数据影响多条数据时候, computed: 对于任何复杂逻辑货一个数据属性在它所依赖的属性发生变化时,也要变化,当一个属性收到多个属性影响的时候

vue生命周期理解

new Vue()

vuex使用极其理解

vuex 是什么

vuex 实现了一个单项数据流,在全局拥有一个state存放数据,当组件更改state中的数据时,须通过mutation提交修改信息,mutation同时提供了订阅者模式供外部插件调用获取state数据的更新。 而当所有异步操作货批量的同步操作需要走action,但action也是无法直接修改state的,还是需要通过mutation来修改state的数据,根据state变化重新渲染数据

核心概念是什么

state、mutation、action、getter、model

怎么做数据存储

什么情况下使用 vuex

nextTick原理

它可以再DOM更新完毕之后执行一个回调

尽管MVVM框架并不推荐访问DOM,但有时候确实会有这样的需求,尤其是和第三方插件进行配合的时候,免不了要进行DOM操作。而nextTick就提供了一个桥梁,确保我们操作的事更新后的DOM

vue如何检测到DOM更新完毕呢 mutationObserve 是h5新增属性,用于监听DOM修改事件,能够监听到节点的属性、文本内容、子节点等的改动

事件循环(Event Loop)

在js运行环境中,通常伴随着很多事件的发生,比如用户点击、页面渲染、脚本执行、网络请求,为了协调这些事件的处理,浏览器使用事件循环机制。 简单来说,时间循环会维护一个或多个任务队列(taskqueues),以上提到的事件作为任务源往队列中加入任务,有一个持续执行的线程来处理这些任务,每执行完毕就从队列中移除它,这就是一次事件循环

vue并不是用MO进行DOM变动监听,而是用队列控制的方式达到的,那么vue是如何做到队列控制的呢。settimeout,吧nexttick要执行的代码当做下一个task放入队列 vue的数据响应过程包含:数据更改-》通知watcher->更新DOM,而数据更改不由我们控制,可能在任何时候发生。如果恰巧发生在重绘之前,就会发生多次渲染,这意味着性能浪费。 所以,vue的队列控制是经过深思熟虑的,我们还需了解event loop另一个概念microtask

microtask (微任务)

首选promise(存在兼容性)---降级宏任务(setimmediate、settimeout)

总结:

1、Vue是用异步队列的方式控制DOM更新和nexttick回调的先后执行 2、microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕 3、因为兼容性问题,vue不得不做了microtask想macrotask的降级方案。

vue双向数据绑定的原理

设计思想: 观察者模式

vue3.0

  • 更快 1、虚拟DOM重写 2、优化slots的生成 3、静态树提升 4、静态属性提升 5、基于proxy的响应式系统
  • 更小:通过摇树优化核心库体积
  • 更容易维护: TS + 模块化
  • 更加友好:跨平台:编译器核心和运行时核心与平台无关 更容易使用:改进的ts支持,编辑器能提供强有力的类型检查和错误及警告