这是我参与8月更文挑战的第5天,活动详情查看: 8月更文挑战
1、v-if VS v-for
v-if vs v-for 优先级;
永远不要把 v-if 和 v-for 同时用在同一个元素上
其次:当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级
将 users替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表
避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上
源码目录:
vue/src/compiler/parser/index.js
2、data为什么是函数不是对象?为什么根实例没有限制?
源码目录:
vue/src/core/instance/state.js
多实例的时候保证状态的相互不干扰、不污染;
根实例每次都是new一个,也就是全局范围内它是单例的。源码目录:vue/src/core/util/options.js
结论:
vue组件可能存在多个实例,如果使用对象形式定义data对象,那么状态变更将会影响所有组件实例,这是 不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况。
(数据初始化,会检测data的形式,从而去执行他的具体的执行方式。另外,根实例在创建的时候,在合并选项的时候,它会有实例拿到,只有根实例才有实例,所以它会躲过data选项的校验。而普通组件,它当时不存在实例,走if逻辑会被检测是否data类型,所以无法跳过。)
3、key的作用和原理
patch.js 代码中,patchVnode --> upDatedChildren
a , b, c, d, e ---> a, b, f, d, e
不使用key :
a --> a, b --> b; c --> f; d --> e; 多了一个e ,则创建 e,并追加进该数组末尾;(更新 f 到 c 上;更新了3次,并做了一次创建、插入操作)
使用key:
一次创建、插入
1、key的作用是为了高效的更新虚拟DOM,其原理是vue在patch过程中精准判断2个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
2、另外,若不设置key还可能在列表更新时引发一些隐藏的bug;
3、vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分他们,否则vue只会替换其内部属性而不会触发过渡效果。
源码中找答案:
vue/src/core/vdom/patch.js ----- updateChildren()
key的作用主要是为了高效的更新虚拟dom;
原理:在patch的过程中会执行patchVNode,patchVNode过程中会执行updadeChildren这个方法,它会更新两个新旧子元素,在这个过程中,通过key可以精准的判断,在当前循环中的这两个节点是否是同一个节点。如果没有加key,则永远认为是相同的节点,那就强制的去执行更新操作,从而无法避免频繁更新元素,额外多做很多dom操作,性能差。加了key,diff时,猜测首尾结构的相似性,大多数情况下元素不会发生大量位置上的变化,所以可以高效的结束这个循环,减少大量的更新过程,操作就会很高效。
结论:
1、key的作用主要是为了高校的更新虚拟dom,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
2、另外,不设置key还可能在列表更新时引发一些隐形bug;
3、vue在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
4、vue中diff算法?
1)、diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即diff),将变化的地方更新在真实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n);
2)、vue2.x 中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到变化发生的地方;
3)、vue中的diff执行的时刻是组件实例执行其更新函数时,它会对比上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch;
4)、diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次比对尝试,如果没有找到相同节点,因此整个patch过程非常高效。
5、vue组件化的理解
组件化定义、优化、使用场景和注意事项;强调vue组件化的一些特点;
1)、组件是独立和可复用的代码组织单元。组件系统是Vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用;
2)、组件开发能大幅提高应用开发效率、测试性、复用性等;
3)、组件使用按分类有:页面组件、业务组件、通用组件;
4)、vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于VueComponent,扩展于Vue;
5)、vue中常见组件化技术有:属性prop,自定义事件,插槽等,它们主要用于通信、扩展等;
6)、合理的划分组件,有助于提升应用性能;
7)、组件应该是高内聚、低耦合的;
8)、遵循单向数据流原则。
6、vue设计原则的理解;
未完待续。。。。。(等我组织下语言)
7、mvc、mvvm、mvp的理解;
1)、三者都是框架模式,它们设计的目标都是为了解决Modal和View的耦合问题;
2)、MVC模式出现较早在后端,如Spring MVC、ASP.NET MVC等,在前端领域的早期也有应用,如Backbone.js 它的优点是分层清晰,缺点是数据流混乱,灵活性带来的维护性问题;
3)、MVP模式是MVC的进化模式,Presenter作为中间层负责MV通信,解决了两者耦合问题,但P层过于臃肿会导致维护问题;
4)、MVVM模式在前端领域有广泛应用,它不仅解决MV耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和DOM操作代码,在提高开发效率、可读性同时还保持了优越的性能表现。
8、vue性能优化方法;
8-1、路由懒加载;
8-2、keep-alive缓存页面;
8-3、使用 v-show 复用DOM;
8-4、v-for遍历避免同时使用v-if;
8-5、长列表性能优化;
1)、如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化;Object.freeze() 属性冻结;
2)、如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容;
8-6、事件的销毁;
8-7、图片懒加载;
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载;如
8-8、第三方插件按需引入;
8-9、无状态组件标记为函数式组件;
8-10、子组件分割;
8-11、变量本地化;
8-12、SSR;
9、vue3的特性;
9-1、更快;
1)、虚拟DOM的重写;
2)、优化slots的生成;
3)、静态树提升;
4)、静态属性提升;
5)、基于Proxy的响应式系统;
9-2、更小
通过摇树优化核心组件库体积;
9-3、更容易维护:Ts + 模块化;
9-4、更加友好;
跨平台:编译器核心和运行时核心与平台无关,使得vue更容易与任何平台(Web、Android、iOS)一起使用;
9-5、更容易使用;
1)、改进的Ts支持,编译器能提供强有力的类型检查和错误及警告;
2)、更好的调试与支持;
3)、独立的响应化模块;
4)、Composition API;
10、vue响应式
回答范例:
1、所谓数据响应式就是能够使数据变化可以被检测并对这种变化作出响应的机制;
2、mvvm框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理。
3、以vue为例说明,通过数据响应式加上虚拟DOM和patch算法,可以使我们只需要操作数据,完全不用接触繁琐的dom操作,从而大大提升开发效率,降低开发难度。
4、vue2中的数据响应式会根据数据类型来做不同处理,如果是对象则采用Object.defineProperty()的方法定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应,如果是数组则通过覆盖该数组原型的方法,扩展他的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set / delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。
5、为了解决这些问题,vue3重新编写了这一部分的实现:利用ES6的Proxy机制代理要响应化的数据,它有很多好处,编程体验使一致的,不需要使用特殊的api,初始化性能和内存消耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的reactivity包,使得我们可以更灵活的使用它,我们甚至不用引入vue都可以体验。
11、vue组件间通信;
1)、props
2)、on
3)、children
4)、listeners
5)、ref
6)、$root
7)、provide / inject eventbus
8)、vuex
10、vuex的作用及理解;
10-1、vuex是vue专用的状态管理库。它以全局方式集中管理应用的状态,并且可以保证状态变更的可预测性;
10-2、vuex主要解决的问题是多组件之间状态共享的问题,利用各种组件通信方式,
12、vue-router如何保护指定路由的安全;
1)、阐述vue-router中路由保护策略;
2)、描述具体实现方式;
3)、简单说一下它们是怎么生效的;
12-1、vue-router中保护路由安全通常使用导航守卫来做,通过设置路由导航钩子函数的方式添加守卫函数,在里面判断用户的登录状态和权限,从而达到保护指定路由的目的;
12-2、具体实现有几个层级:全局前置守卫、路由独享守卫或组件内守卫。以全局守卫为例,可以使用router.beforeEach((to,from,next) => {}) 方式设置守卫,每次路由导航时,都会执行该守卫,从而检查当前用户是否可以继续导航,通过给next函数传递多种参数达到不同的目的,比如如果禁止用户继续导航可以传递 next(false),正常放行可以不传递参数,传递path 字符串可以重定向到一个新的地址等等。
12-3、这些钩子函数之所以能够生效,也和vue-router工作方式有关,像beforeEach只是注册一个hook,当路由发生变化,router准备导航之前会批量执行这些hooks,并且把目标路由to,当前路由from,以及后续处理函数next 传递给我们的hook。
Q:
全局守卫、路由独享守卫和组件内守卫区别?
项目中路由守卫怎么做的?
前后端路由一样吗?
前端路由是用什么方式实现的?
next方法是怎么实现的?
13、nextTick的作用及实现原理;
1)、nextTick是啥?下一个定义;
2)、为什么需要它,用异步更新队列实现原理解释;
3)、在什么地方用它?
4)、如何使用nextTick;
5)、最后说出源码实现
13-1、是Vue提供的一个全局API,由于vue的异步更新策略导致我们数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom 状态,就需要使用这个方法;
13-2、Vue在更新DOM时是异步执行的,只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher 被多次触发,它只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常必要的
。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
13-3、所以当我们想在修改数据后立即看到dom执行结果就需要用到nextTick方法,传一个回调函数进去,在里面执行dom操作即可;
14、vue响应式原理;
啥是响应式?
为啥vue需要响应式?
它能给我们带来啥好处?
vue响应式是怎么实现的?有哪些优缺点?
vue3中的响应式的新变化?
14-1、所谓响应式就是能够是数据变化可以被检测并对这种变化做出响应的机制。
14-2、mvvm框架中要解决的一个核心问题就是连续数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理;
14-3、以vue为例说明,通过数据响应式加上虚拟DOM和patch算法,可以使我们只需要操作数据,完全不用接触繁琐的dom操作,从而大大提升开发阿效率,降低开发难度;
14-4、vue2中的数据响应式会根据类型来做处理,如果是对象则采用Object.defineProperty() 的方式定义数据拦截,当数据被访问或发生变化时,我们感知并做出响应;如果是数据则通过覆盖该数组原型的方法,扩展它的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会导致性能损失;新增或删除属性时需要用户使用Vue.set / delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。
14-5、为了解决这些问题,vue3重新编写了这一部分的实现:利用ES6的Proxy机制代理要响应化的数据,它有很多好处,编程体验是一致的,不需要使用特殊api,初始化性能和内耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的reactivity包,使得我们可以更灵活的使用它,我们甚至不需要引入vue都可以体验。
个人关于vue相关面试问题的集合,未尽,精力充沛时会再次编辑。欢迎指教。