当前位置: 首页 > Web前端 > vue.js

前端实训:Vue面试题分享

时间:2023-04-01 00:30:29 vue.js

1.请谈谈对响应式数据的理解?核心答案:当数组和对象类型的值发生变化时,如何劫持它们。对象内部使用defineReactive方法使用Object.defineProperty劫持属性(只有存在的属性才会被劫持),数组通过重写数组方法实现。这里可以在回答的时候带出一些相关的知识点(比如通过递归劫持多层对象,Vue3顺便使用proxy实现响应式数据)。补充回答:内部依赖收集是如何实现的?每个属性都有自己的dep属性,里面存放着它所依赖的watcher。当属性发生变化时,会通知其对应的watcher更新前端训练机构(其实后面会提到,每个object类型本身也有一个dep属性,在$set面试题中解释)设置的面试题)这里可以引出性能优化相关的内容(1)如果object层次太深,性能会很差(2)不要把不需要响应数据的内容放在data(3)Object.freeze()可以快速冻结数据Mock:letstate={count:0};//app.innerHTML=state.count;//1.将数据变成响应式数据letactive;functiondefineReactive(obj){for(letkeyinobj){letvalue=obj[key];letdep=[];Object.defineProperty(obj,key,{get(){if(active){dep.push(active);}返回值;},set(newValue){value=newValue;dep.forEach(fn=>fn());}});}}defineReactive(state);constwatcher=(fn)=>{active=fn;fn();active=null;}watcher(()=>{app.innerHTML=state.count;});watcher(()=>{console.log(州.计数)});2、Vue如何检测数组变化?核心答案:考虑到性能原因,数组没有使用defineProperty拦截数组的每一项,而是选择了重写重写数组的方法(push、shift、pop、splice、unshift、sort、reverse)。补充回答:在Vue中修改数组的索引和长度是无法监听的。需要通过以上7种变异方式修改数组,触发数组对应的watcher更新。如果数组中存在对象数据类型,也会进行递归劫持。那么如果要改变索引来更新数据怎么办呢?可以通过Vue.$set()="核心内部使用拼接的方式来快速Mock:letstate=[1,2,3];letoriginArray=Array.prototype;letarrayMethods=Object.create(originArray);functiondefineReactive(obj){arrayMethods.push=function(...args){originArray.push.call(this,...args);render();}obj.__proto__=arrayMethods;}defineReactive(state);functionrender(){app.innerHTML=state;}render();state.push(4);3.Vue中模板编译的原理?核心答案:如何将模板转化为render函数(这里,我们开发时应该尽量不要使用模板,因为模板转换为render方法需要在运行时进行编译,会造成性能损失,同时引用编译包vue的体积也会变大.默认.vue文件中的模板处理是通过vue-loader处理的,并没有在运行时编译——后面我们会说defaul中引入的vue.jstvue项目没有with编译器模块)。1.将template模板转换成ast语法树——parserHTML2。静态标记静态语法——markUp3。重新生成代码-codeGen补充回答:模板引擎的实现原理是在vue-loader中处理模板,通过newFunction+with属性主要依赖于vue-template-compiler模块的快速Mock:4.生命周期钩子是如何实现的?核心答案:Vue的生命周期钩子只是一个回调函数。创建组件实例时,会调用相应的钩子方法。补充回答:callHook方法主要用于内部调用相应的方法。核心是一个发布-订阅模型,订阅钩子(内部存储在一个数组中)并在相应的阶段发布!快速模拟:functionmergeHook(parentVal,childValue){if(childValue){if(parentVal){returnparentVal.concat(childValue);}else{return[childValue]}}else{returnparentVal;}}functionmergeOptions(parent,child){letopts={};for(letkeyinchild){opts[key]=mergeHook(parent[key],child[key]);}returnopts;}functioncallHook(vm,key){vm.options[key].forEach(hook=>hook());}functionVue(options){this.options=mergeOptions(this.constructor.options,options);callHook(this,'beforeCreate');}Vue.options={}newVue({beforeCreate(){console.log('beforecreate')}})5、Vue.mixin的使用场景和原理是什么?核心答案:Vue.mixin的作用是抽取公共业务逻辑。原理类似于“对象继承”。组件初始化时会调用mergeOptions方法进行合并,使用策略模式合并不同的属性。如果混入的数据与自身组件中的数据冲突,则采用“就近原则”,以组件中的数据为准。补充回答:mixin存在“命名冲突问题”“依赖问题”“数据源问题”等诸多缺陷。这里我强调一下,mixin的数据是不会对外共享的!快速模拟:Vue.mixin=function(obj){this.options=mergeOptions(this.options,obj);}Vue.mixin({beforeCreate(){console.log('beforecreateok')}})6.nextTick在哪里使用?原理是?核心答案:nextTick中的回调是在下一个DOM更新周期结束后执行的延迟回调。修改数据后立即使用此方法获取更新后的DOM。原理是异步方法(promise、mutationObserver、setImmediate、setTimeout)经常和事件循环(macrotasks和microtasks)一起被问补充回答:Vue多次更新数据,最终会进行批量更新。内部调用是nextTick实现了延迟更新,用户自定义的nextTick中的回调会延迟到更新完成,从而获取到更新后的DOM。快速模拟:让cbs=[];让pending=false;函数flushCallbacks(){cbs.forEach(fn=>fn());}函数nextTick(fn){cbs.push(fn);如果(!pending){pending=true;setTimeout(()=>{flushCallbacks();},0);}}functionrender(){console.log('rerender');};nextTick(render)nextTick(render)nextTick(render);console.log('sync...')7.为什么Vue需要虚拟DOM?核心答案:VirtualDOM使用js对象来描述真实的DOM。它是对真实DOM的抽象。由于直接操作DOM性能低但js层操作效率高,可以将DOM操作转化为对象操作,最后通过diff算法Diff进行比较,更新DOM(减少对真实DOM的操作).VirtualDOM不依赖于真实的平台环境,因此也可以实现跨平台。补充回答:虚拟DOM的实现是普通对象对真实节点的描述,包含tag、data、children等属性。(本质上是JS和DOM之间的缓存)8.Vue中diff原理的核心答案:Vue的diff算法是层次比较,不考虑跨层次比较。内部采用深度递归+双指针的方式进行比较。补充回答:1.先比较是否是同一个节点2.比较同一个节点的属性,复用旧节点3.比较子节点,考虑老节点和新节点儿子的情况4.优化comparison:head,tail,headandtail,Tail5.multiplexingforcomparisonandsearchVue3中使用最长增量子序列实现diff算法9.Vue.set方法是如何实现的?核心答案:为什么$set可以触发更新,我们给对象和数组本身都添加了dep属性。当给一个对象添加一个不存在的属性时,会触发该对象依赖的watcher进行更新。在修改数组索引时,我们调用数组本身的splice方法来更新数组。exportfunctionset(target:Array|Object,key:any,val:any):any{//1.如果开发环境target没有定义或者是基本类型,if(process.env.NODE_ENV!=='production'&&(isUndef(target)||isPrimitive(target))){warn(无法在未定义、null或原始值上设置响应式属性:${(target:any)})}//2.如果是数组Vue.set(array,1,100);调用我们重写的拼接方法(这可以更新视图)if(Array.isArray(target)&&isValidArrayIndex(key)){target.length=Math.max(target.length,key)target.splice(key,1,val)returnval}//3.如果是If(keyintarget&&!(keyinObject.prototype)){target[key]=valreturnval}constob=(target:any).__ob__//4.如果是Vueinstanceorrootdatadata报错if(target._isVue||(ob&&ob.vmCount)){process.env.NODE_ENV!=='production'&&warn('AvoidaddingreactivepropertiestoaVueinstanceoritsroot$data'+'atruntime-在data选项中预先声明它。')returnval}//5.如果它不是响应式的,则不需要定义为响应式属性if(!ob){target[key]=valreturnval}//6.将属性定义为响应式defineReactive(ob.value,key,val)//7.通知视图更新ob.dep.notify()returnval}10.Vue有哪些生命周期方法?通常在哪一步发起请求,为什么?核心答案:beforeCreate在实例初始化后调用,beforedataobserver和event/watcher事件配置创建在实例创建后调用。在这一步中,实例完成了以下配置:数据观察者、属性和方法的操作、watch/event事件回调。这里在挂载开始之前没有调用$elbeforeMount:第一次调用相关的渲染函数。在挂载的el被新创建的vm.$el替换并挂载到实例后调用此挂钩。beforeUpdate在更新数据时调用,在重新渲染和修补虚拟DOM之前。updated这个钩子在虚拟DOM由于数据更改而被重新渲染和修补后被调用。beforeDestroy在销毁实例之前调用。在这一步,实例仍然完全可用。destroyed在Vue实例被销毁后调用。调用后,Vue实例指向的所有东西都将被解除绑定,所有事件监听器将被移除,所有子实例将被销毁。服务器端渲染期间不会调用此挂钩。补充回答:创建的实例已经被创建了,因为它是最早触发一些数据和资源请求的。(服务端渲染支持created方法)挂载实例已经挂载,可以进行一些DOM操作。beforeUpdate可以进一步改变这个钩子中的状态,这不会触发额外的重新渲染过程。updated可以执行依赖于DOM的操作。然而,在大多数情况下,您应该避免在此期间更改状态,因为这可能会导致更新无限循环。服务器端渲染期间不会调用此挂钩。destroyed可以进行一些优化操作,清除定时器,解除绑定事件11.Vue组件有什么区别?props和父组件通过passing传递数据给子组件,子组件通过emit触发事件传递数据给父组件。children获取当前组件的父组件和当前组件的子组件$attrs和$listenersA->B->C。Vue2.4开始提供$attrs和$listeners来解决这个问题。在父组件中提供变量,然后通过inject在子组件中注入变量。$refsgetinstanceenvetBuslevelcomponentdatatransfer这种情况下,可以使用centraleventbus方式vuex状态管理(1)props实现:src/core/vdom/create-component.js:101,src/core/instance/init.js:74,scr/core/instance/state:64(2)事件机制实现:src/core/vdom/create-component.js:101,src/core/instance/init.js:74,src/core/instance/events.js:12(3)parent&childrenimplementation:src/core/vdom/create-component.js:47,src/core/instance/lifecycle.js:32(4)provide&injectimplementation:src/core/实例/inject.js:7(5)$attrs&$listener:src/core/instance/render.js:49,src/core/instance/lifecycle.js:215(6)$rsrc/core/vdom/modules/reg.js:2012.$attrs解决了什么问题?有哪些应用场景?不提供/注入解决它所解决的问题?核心答案:$attrs的主要作用是实现数据的批量传输。Provide/inject比较适合应用在插件中,主要是实现跨层数据传递13.Vue组件渲染流程?核心答案:父子组件的渲染顺序父组件的虚拟节点,其中可能包含子组件的标签②创建虚拟节点时,获取组件的定义,使用Vue.extend生成组件的构造函数。③将虚拟节点转换为真实节点时,会创建组件实例,并调用组件的$mount方法。④所以组件的创建过程是先父后子14、为什么Vue中组件的数据是一个函数?核心答案:每次使用组件时,都会实例化组件,调用数据函数返回一个对象作为组件源的数据。这样可以保证多个组件之间的数据互不影响QuickMock:classVue{constructor(options){this.data=options.data();}}letdata=()=>({a:1})letd1=newVue({data});letd2=newVue({data});d1.data.a=100;console.log(d2);//115.请告诉我关于v-if和v-show的不同核心答案:v-if在编译时会被转换成三元表达式,不满足条件时不会渲染这个节点。v-show会被编译成指令,当条件不满足时,控件样式会隐藏对应的节点(其他内部指令会继续执行)扩展答案:尽量不要使用v-if、v-if和v-for用于频繁控制显示和隐藏不要使用v-if源码分析:functiongenIfConditions(conditions:ASTIfConditions,state:CodegenState,altGen?:Function,altEmpty?:string):string{if(!conditions.length){返回altEmpty||'_e()'}constcondition=conditions.shift()if(condition.exp){//如果有表达式return`(${condition.exp})?${//将表达式作为条件拼接成一个元素genTernaryExp(condition.block)}:${genIfConditions(conditions,state,altGen,altEmpty)}`}else{return${genTernaryExp(condition.block)}//没有像v-else这样的表达式直接生成元素}//v-ifwithv-once应该生成类似(a)?_m(0):_m(1)functiongenTernaryExp(el){returnaltGen?altGen(el,state):el.once?genOnce(el,state):genElement(el,state)}}v-show源码分析:{bind(el:any,{value}:VNodeDirective,vnode:VNodeWithData){constoriginalDisplay=el.__vOriginalDisplay=el.style.display==='无'?'':el.style.display//获取原始显示值el.style.display=value?originalDisplay:'none'//根据属性控制显示或隐藏}}文章来源:前端百科