当前位置: 首页 > 科技观察

《面试题》20+Vue面试题整理

时间:2023-03-19 02:24:05 科技观察

从镜头的粗细和黄黑格子衬衫,我察觉到坐在我前面的面试官应该是个坏人。像往常一样,我将花3分钟时间做自我介绍。这段时间,为了避免尴尬,我一直盯着面试官的眉毛中间看,但是面试官显然对我的经历不是很感兴趣。他在1点30分打断了我。你认为你最擅长的技术栈是什么?Vue,我很喜欢裕达。我最近刚刚发布了Vue的第一部纪录片,非常值得一看。0.你能谈谈MVVM吗?MVVM是Model-View-ViewModel的缩写,就是将MVC中的Controller进化为ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View层和Model层之间的桥梁。数据将绑定到viewModel层并自动将数据呈现到页面。当视图发生变化时,会通知viewModel层更新数据。1、简单说说Vue2.x响应式数据的原理Vue在初始化数据的时候,会使用Object.defineProperty重新定义数据中的所有属性。当页面使用相应的属性时,会先收集依赖(收集当前组件的watcher),如果属性发生变化,会通知相关依赖进行更新(发布和订阅)。2、你知道Vue3.x响应式数据的原理吗?(幸好我看过,这个我不介意)Vue3.x使用Proxy代替了Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,拦截方式多达13种。并且作为新标准,浏览器制造商将专注于持续的性能优化。Proxy只会代理第一层对象,那么Vue3是如何处理这个问题的呢?(很简单)判断Reflect.get的返回值是否为Object,如果是则使用react方法作为代理,从而实现深度观察。在监控数组的时候,get/set可能会被触发多次,那么如何防止多次触发呢?我们可以判断key是不是当前代理对象target本身的属性,或者旧值是否等于新值。只有满足以上两个条件之一,触发器才会被执行。面试官抬起头。心想(这小子还行,比前两个好,应该多多少少看过Vue3的源码)3、说说vue2.x中如何使用函数劫持来监听数组变化,重写数组方法,Vue在data中重写了数组的原型链,指向自己定义的数组原型方法。这样调用arrayapi的时候,就可以通知依赖更新了。如果数组中包含引用类型,则会再次监控数组中引用类型的递归遍历。这样就实现了监听数组变化。(能问出这个问题的面试官比较注重深度,这些套路操作要牢记)(原型链详见我的另一篇专栏)本文将带你了解JavaScript原型链4.你知道nextTick吗?原理是什么?在下一个DOM更新周期结束后执行的延迟回调。nextTick主要使用宏任务和微任务。根据执行环境尝试使用PromiseMutationObserversetImmediate。如果以上都不起作用,请使用setTimeout定义一个异步方法。多次调用nextTick会将方法存入队列,通过这个异步方法清空当前队列。(关于宏任务、微任务和事件循环,请参考我的另外两个专栏)(看到这里你会发现,其实框架最终会考验你原生的JavaScript技能)JavaScript在浏览器Node中的事件循环.js事件循环5.说说Vue的生命周期。beforeCreate是newVue()之后触发的第一个钩子。目前阶段,data、methods、computed、watch上的数据和方法是无法访问的。created发生在实例创建之后,当前阶段已经完成了数据观察,即可以使用和更改数据,更改这里的数据不会触发update函数。可以做一些初步的数据获取,现阶段还不能和Dom交互。如果你愿意,你可以通过vm.$nextTick访问Dom。beforeMount发生在挂载之前,模板模板还没有被导入渲染函数进行编译之前。目前阶段,虚拟Dom已经创建完成,即将开始渲染。此时也可以更改数据而不触发更新。mounted发生在挂载完成之后。现阶段挂载了真正的Dom,数据双向绑定,可以访问到Dom节点,可以通过$refs属性来操作Dom。beforeUpdate发生在更新之前,即响应式数据更新时,在虚拟dom重新渲染之前触发。您可以在当前阶段更改数据而不会导致重新渲染。updated发生在更新完成后,当前stage组件Dom已经更新完毕。小心避免在这段时间内更改数据,因为这可能会导致更新无限循环。beforeDestroy发生在实例销毁之前,当前阶段实例可以完全使用,此时我们可以进行收尾工作,比如清空定时器。destroyed发生在实例销毁之后,此时只剩下dom壳。组件拆了,数据绑定拆了,监听器拆了,子实例全部销毁了。(如果对Vue的生命周期详解感兴趣,也请移步我的其他专栏)从源码解读Vue生命周期,会给面试官留下深刻印象6、你的接口请求一般放在哪个生命周期?接口请求一般放在mounted中,但是需要注意的是服务端渲染时不支持mounted,需要放在created中。7.说一下,Computed和WatchComputed本质上是一个带缓存的watcher,当依赖的属性发生变化时,view就会更新。适用于计算对性能消耗较大的计算场景。当表达式过于复杂时,在模板中放入过多的逻辑会使模板难以维护,可以在计算属性中处理复杂的逻辑。Watch没有缓存,更多的是一个观察功能,可以监听某些数据并执行回调。当我们需要对对象中的属性进行深度监控时,可以开启deep:true选项,这样对象中的每一项都会被监控到。这会导致性能问题。优化的话,可以用字符串的形式来监控。如果没有写入组件,不要忘记使用unWatch手动退出。8、先说一下v-if和v-show的区别。当条件不成立时,v-if不会渲染DOM元素,v-show对样式(display)进行操作,切换当前DOM的显示和隐藏。9、为什么组件中的数据是一个函数?如果多次重用一个组件,将创建多个实例。本质上,这些实例都使用相同的构造函数。如果data是一个对象,那么这个对象就是一个引用类型,会影响到所有的实例。所以为了保证不同组件实例之间数据不冲突,数据必须是一个函数。10、说说v-model的原理v-model本质上是一个语法糖,可以看成是值+输入法的语法糖。可以通过model属性的prop和event属性进行自定义。原生的v-model会根据不同的标签生成不同的事件和属性。11、Vue事件绑定原理先说下原生事件绑定是通过addEventListener绑定到真实元素上,而组件事件绑定是通过Vue自定义的$on实现的。面试官:(这小子基础还不错,接下来还要上难度)12、你知道Vue模板编译原理吗?能简单说明一下吗?简单的说,Vue的编译过程就是将模板转换成渲染函数的过程。它将经历以下阶段:生成AST树优化Codegen首先解析模板并生成AST语法树(一种描述整个模板的JavaScript对象形式)。使用大量正则表达式解析模板,遇到标签和文本时,会执行相应的钩子进行相关处理。Vue的数据是响应式的,但并不是模板中的所有数据都是响应式的。有些数据在第一次渲染后不会发生变化,对应的DOM也不会发生变化。那么优化过程就是深度遍历AST树,根据相关条件标记树节点。我们可以跳过这些标记节点(静态节点)的比较,这在运行时极大地优化了模板。编译的最后一步是将优化后的AST树转换为可执行代码。面试官:(精神哥,有东西,难度增加,不信麻烦你)13.说说Vue2.x和Vue3.x渲染器的diff算法然后比较子节点和先判断一方有子节点,另一方没有子节点的情况(如果新的孩子没有子节点,则去掉旧的子节点)比较双方子节点的情况(corediff)递归比较子节点和正常Diff两棵树的时间复杂度是O(n^3),但是实际中我们很少跨层移动DOM,所以Vue优化Diff从O(n^3)->O(n),只有当新旧children为多个子节点,需要使用核心Diff算法进行同级比较。Vue2的核心Diff算法采用了双端比较的算法。同时从新旧孩子两端开始比较,根据key值找到可重用的节点,然后进行相关操作。与React的Diff算法相比,可以减少相同情况下移动节点的次数,减少不必要的性能损失,更加优雅。Vue3.x借鉴了ivi算法和inferno算法在创建VNode时判断其类型,在mount/patch过程中使用位运算判断VNode的类型。在此基础上,配合核心Diff算法,使得性能较Vue2.x有所提升。(具体实现可以结合Vue3.x源码查看)该算法同样采用了动态规划的思想求解最长递归子序列。(看到这里,你也会发现框架中处处隐藏着数据结构和算法的魅力)面试官:(是的,好像是一棵小苗,但是自我介绍有点无聊,下一个问题)(基础练习,不要6)14.说一下虚拟Dom的作用和关键属性,因为在浏览器中操作DOM的开销非常大。频繁操作DOM会导致一定的性能问题。这就是虚拟Dom的原因。Vue2的VirtualDOM借鉴了开源库snabbdom的实现。VirtualDOM的本质是用一个原生的JS对象来描述一个DOM节点。它是对真实DOM的抽象层。(即源码中的VNode类,定义在src/core/vdom/vnode.js中)VirtualDOM映射到真实DOM要经过VNodecreate、diff、patch等阶段的作用关键是尽可能重用DOM元素。当新旧子节点中的节点顺序不同时,最好的操作应该是通过移动元素的位置来达到更新的目的。新旧子节点中需要保存映射关系,以便在旧子节点中找到可复用的节点。key是节点在children中的唯一标识。15、你了解keep-alive吗?keep-alive可以实现组件缓存,切换组件时不会卸载当前组件。两个常用的属性include/exclude允许有条件地缓存组件。activated/deactivated两个生命周期用于了解当前组件是否处于活动状态。keep-alive也用到了LRU(LeastRecentlyUsed)算法。(又是数据结构和算法,原来算法在前端有那么多应用。)16.Vue中组件生命周期的调用顺序先说说组件的调用顺序先父后子,以及组件的调用顺序渲染是先子后父。组件的销毁操作是先父后子,销毁顺序是先子后父。加载渲染进程parentbeforeCreate->parentcreated->parentbeforeMount->childbeforeCreate->childcreated->childbeforeMount->childmounted->parentmounted子组件更新流程parentbeforeUpdate->childbeforeUpdate->childupdated->parentupdatedparentcomponentupdateprocessparentbeforeUpdate->parentupdateddestructionprocessparentbeforeDestroy->childbeforeDestroy->childdestroyed->parentdestroyed17.Vue2.x组件通信方式有哪些?父子组件通信Parent->childprops,child->parent$on,$emit获取父子组件实例$parent,$childrenRef通过调用组件属性或方法获取实例Provide,inject官方不推荐,而是写组件libraries很常见的是兄弟组件通信EventBus实现跨组件通信Vue.prototype.$bus=newVueVuex跨级组件通信Vuex$attrs,$listeners提供,inject18.SSR你懂吗?SSR即服务端渲染,即在服务端完成Vue在客户端将标签渲染成HTML的工作,然后将HTML直接返回给客户端。SSR具有更好的SEO和更快的首屏加载优势。但是,它也有一些缺点。比如我们的发展条件会受到限制。服务器端渲染只支持两个钩子,beforeCreate和created。当我们需要一些外部扩展库时,需要进行特殊处理。服务器端渲染应用程序也需要在Node.js运行环境中。另外,服务器会有更大的负载要求。19、你做过哪些Vue性能优化?在编码阶段尽量减少数据中的数据,数据中的数据会添加getter和setter,并收集相应的watchers。v-if和v-for不能一起使用。如果需要使用v-for为每个元素绑定事件,请使用事件代理。SPA页面使用keep-alive缓存组件。更多情况下,使用v-if代替v-showkey,保证路由懒加载、异步组件防抖、限流的唯一使用。第三方模块按需导入长列表滚动查看Region动态加载图片延迟加载SEO优化预渲染服务器端渲染SSR打包优化压缩代码TreeShaking/ScopeHoisting客户端缓存,服务器缓存)优化,启用gzip压缩服务器等(优化是个大工程,会涉及到很多方面,这里是另外一个专栏的应用。)20.hash路由和history路由实现原理先说说location的值,hash其实就是个东西在URL中的#之后。History实际上是使用HTML5中提供的API实现的,主要包括history.pushState()和history.replaceState()。面试官拿起身边的冷咖啡喝了一口。(我不能请这孩子下来吗?)