1. vue2和vue3中的diff算法有什么区别?
vue中diff算法比较的是虚拟节点上面的tag,key是否相同,并且是同级比较的;如果不同直接创建新节点进行替换,如果相同就复用dom并且比对更新属性事件等;如果有子元素就通过diff算法进行比对;
vue2中diff使用了双指针,分别是新旧虚拟节点列表的前后四个指针,通过对比新前旧前,新后旧后,新后和旧前,旧前和新后,如果新前和旧前或新后和旧后相同就递归更新它们的属性文本等,并且分别向下或向上移动它们的指针;新后和旧前相同递归更新属性文本等,并且把旧前移动到旧后的后面,向上和向下移动指针;新前和旧后相同递归更新属性文本等,并且把旧后移动到旧前的前面;如果以上都不相等,那么就获取到旧列表中旧前和旧后之间的节点的key对应的index,映射到一个对象中;在这个对象中找到新前节点,找到就把旧列表中的这个节点移动到旧前的前面,如果没有找到就创建这个节点插入到旧前的前面;双指针循环完毕,再判断新前是否大于新后,如果大于表示旧列表中可能还有剩余节点,直接遍历删除;判断旧前是否大于旧后,如果大于表示新列表中有新增的节点,直接遍历创建插入到新后的下个节点的前面;
vue2中diff算法的缺点:对于乱序vue2中会把旧列表中的每个dom移动到旧前的前面;而vue3中通过最长递增子序列进行了优化;
vue3中先循环新旧列表判断前前是否相同相同就进行更新属性等,如果不同直接跳出循环;再遍历新旧列表,判断后后是否相同,如果相同就更新属性,不同直接跳出循环;这样就处理完了前后相同的节点;再判断旧列表是否遍历完成,如果完成并且新列表还没有遍历完成就直接创建这些节点插入到新后的下个节点的前面或者追加到列表后面;再判断新列表是否遍历完成,如果完成并且旧列表没有遍历完成,说明有删除的节点,直接遍历删除旧列表中的节点;如果以上都不满足剩下就是乱序的列表;找到新列表中未处理的节点,把它们的key和索引映射到map中,遍历旧列表中未处理的节点,如果当前节点在映射的map中不存在,表示需要删除直接删除;否则就进行更新并且保存新列表中未处理节点在旧列表中对应节点的索引加1存到一个数组中;通过最长递增子序列算法找出这个数组中最长的递增子序列;倒着遍历新列表未处理节点的长度和最长的递增子序列,判断它们的值是否相同,相同直接跳过,不同就进行移动,如果为0就创建;
2. 说说你对vue的了解
vue是声明式框架,无需关注内部的具体实现,通过提供的api直接使用;vue中数据的绑定采用了MVVM模式,vue不是一个完整的mvvm模式,因为它可以直接操作dom,它只是借鉴了MVVM模式的思想;vue中采用了模板语法,通过模板编译生成对应的虚拟dom,虚拟dom可以减少对真实dom的操作,通过diff算法对比找出dom需要更新的地方实现最小代价的更新,从而性能上有很大的提升,虚拟dom还可以实现跨平台;vue分为编译时和运行时,编译时带有模板编译的包因此它的体积较大,而运行时不包含模板编译,模板编译可以放在打包阶段完成;vue支持组件化,一个大的页面可以拆分成多个组件,页面中某个地方的变化只要更新对应的组件即可,不用更新整个页面;
3.什么是单页面/SPA应用
SPA是单页面应用,整个应用中只有一个html页面,打包生成的其他资源都引入到这个html页面中。页面中通过js动态的进行渲染;js监听到路由的变化渲染对应的页面;
单页面应用的优点:用户体验好,页面的切换不需要刷新整个页面;便于维护;
缺点:不利于SEO;首屏加载较慢;解决:进行预渲染;通过服务端渲染;
4. 什么是虚拟dom?什么是ast抽象语法树?它们有什么区别?
虚拟dom就是一个js对象,通过对象的属性来描述一个真实的dom;这个对象就是虚拟dom; ast抽象语法树也是一个对象,它是通过规定的语法进行描述;它们的区别在于虚拟dom可以根据开发者自己的需求随意描述一个真实的dom;而抽象语法树只能按照标准规定来;抽象语法树主要用在编译器和解析器中对代码的解析,优化和转换等操作;
5. 为什么要使用虚拟dom?
因为频繁的操作真实dom比较消耗性能,通过虚拟dom结合diff算法找出差异实现最小化更新dom;这样可以提高频繁操作dom的性能;而且通过虚拟dom可以实现跨平台;
6. vue中虚拟dom如何生成?
虚拟dom通过模板编译,解析模板中的内容生成对应的ast抽象语法树,根据抽象语法树中不同类型的节点使用相应的函数进行包裹,通过with和Function进行包裹生成render函数,执行render函数就可以获取到虚拟dom;
7. 虚拟dom如何做diff算法?
首次模板编译的时候就会生成render函数,执行render函数会获取到虚拟dom,根据虚拟dom通过watcher和patch创建真实的dom;当数据变化的时候会重新执行watcher中的render函数,获取到新的虚拟dom,通过patch就可以拿到新旧虚拟dom进行diff算法比对;
8. 对组件化的理解?
组件是对UI的封装,组件是由模板,事件,属性,插槽,生命周期等组成;复用组件可以提高开发效率,组件可以实现局部更新,vue中每个组件都是一个watcher或effect。当数据变化的时候只更新当前数据所在的组件,不用整个页面进行更新;所以合理的拆分组件有利于提高开发效率和性能优化;但是不能过度的拆分,因为每个组件都是一个watcher,过多的watcher也是一种性能消耗;
9. vue中通过数据劫持可以精确的知道数据的变化,为什么还需要diff算法?
通过数据劫持可以知道数据的变化,数据的变化会通知相应的watcher进行更新,如果每条数据都对应一个watcher那么一个页面中有几万条数据那就对应几万个Watcher,每个watcher是一个函数或类。它们是占用内存的,因此数量多了就会占用大量的内存,导致性能低下;最后还是通过组件和watcher实现;
10. 什么是响应式?
响应式就是数据变化的时候能够知道数据变化了并且根据数据变化做一些处理;vue2中通过Object.defineProperty给对象上的每个属性添加setter和getter方法;当读取属性的时候getter中进行收集依赖,在修改数据的时候在Setter中更新依赖;对于深度的对象通过递归的方法给每个对象下的属性添加setter和getter;对于新增和删除是监听不到的,因此vue2单独提供了两个方法分别为$set和$delete方法进行处理;对于数组vue2没有监听数组的每一项,而是重写了修改数组的七个方法,分别为sort,reverse,splice,push,pop,unshift,shift;通过这七个方法实现对数组的监听;而vue3中通过proxy对对象进行了代理,对于深层次的对象没有使用递归,而是在获取的时候进行判断是否是对象,是对象就通过proxy再进行代理;proxy可以直接代理数组,并且也重写了修改数组的七个方法;对于Set和Map对象也重写代理了它们各自的方法;
11. vue中对于数组是怎么劫持的?
vue2中对于数组只会重写了能够改变原数组的七个方法,分别为sort,reverse,splice,push,pop,unshift,shift;通过重写这七个方法可以在修改数组的时候实现更新依赖;对于数组中的对象通过遍历数组递归通过Object.defineProperty进行设置setter和getter;通过索引直接修改数组中的某一项是不会被监听到的;对于数组的依赖收集,数组本身是一个对象,所以它有Setter和getter,在使用数组的地方就会收集依赖,修改数组中的某一项就是更新数组本身收集到的依赖;
vue3中对于数组和对象都是通过Proxy进行代理,对于改变原数组的七个方法也进行了重写;因此vue3中可以通过索引修改数组中的某一项是可以被监听到的;
12. vue中如何进行依赖收集?
vue2中的依赖就是watcher;通过Object.defineProperty给数据添加监听,读取属性的时候在getter中进行依赖收集,把当前数据所在的依赖watcher通过Dep管理器进行收集;在修改数据的时候在Setter中通过Dep遍历依赖进行更新;整个过程使用了发布订阅者模式; vue3中依赖就是effect;通过Proxy代理数据实现监听数据;在getter中收集依赖,它没有Dep管理器,而是通过weakMap,map和Set进行存储,把对象和属性和依赖对应存储起来;在数据变化的时候通过对象和key获取到对应的effect进行更新;
13. $set是怎么实现的?
vue2中才有这个方法,此方法用来给对象或数组新增属性或数据;此方法内部对于数组通过splice进行添加数据;对于非响应式对象直接添加属性;对于是vue的实例或vue实例上的根对象直接警告返回;对于响应式对象通过defineReactive添加属性;最后进行依赖的更新;
14. v-if和v-show的区别?
v-if会被编译成一个三元表达式,如果满足条件就创建dom渲染到页面上,如果不满足条件就删除dom;添加一个注释节点; v-show是会保存之前的display的值,如果满足条件display设置为之前的值,不满足就设置为none; v-if的优先级高于v-show;
15. computed和watcher?
computed: 计算属性只有在使用的时候才执行它的回调函数,计算属性具有缓存,当依赖的数据没有发生变化的时候会从缓存中获取返回,当数据变化的时候执行回调返回最新的值;计算属性中不支持异步操作,可以用来简化一些复杂的表达式;
vue2中的计算属性是一个watcher,内部会为当前属性创建一个watcher并且进行缓存,在下次获取的时候会从缓存中获取到对应的watcher,通过watcher上的dirty判断数据是否变化,如果变化了重新执行回调返回新的值,否则直接返回之前的值;
vue3中的计算属性是一个effect,通过一个开关和schduler来判断值是否变化,值变化了就会执行scheduler并且把开关设置为true,再次取值的时候通过判断开关为true表示值变化了,执行effect的run方法获取新值,没有变化直接返回之前的值;
watcher:当数据变化的时候就会执行回调;并且支持异步处理;vue3中的每个watcher内部都是由一个effect和自定义的scheduler组成,如果监听的是一个对象,那么就通过一个函数包装,函数内部递归这个对象读取它的属性,这样就可以给每个属性收集到当前的effect,当数据变化的时候通过effect进行更新获取到新值传递给回调函数;
16. ref和reactive的区别?
用法上:ref用于给基本类型的数据设置为响应式的,但是也可以设置对象类型;reactive只能设置对象类型,对于基本类型的会报错;
原理上:ref用于设置基本类型所以它内部使用一个类,通过类的value来修改和获取值,在获取值的时候执行value的getter方法进行依赖收集;在修改值的时候执行value的setter方法进行依赖的更新;如果传递的是一个对象。它内部会进行判断采用reactive进行代理;
对于reactive它内部采用了proxy进行代理,所以它只能代理对象,不能代理基本类型,它代理了对象的get,set,delete,has等方法,对于Set和Map对象也分别代理了它们各自的方法;
17. watch和watchEffect的区别?
用法上:watch可以监听具体的数据,并且传递一个回调函数,函数中可以获取到数据变化的前后值;watchEffect可以传递一个函数,用于监听函数中响应式数据的变化;
原理上:watch和watchEffect内部都是由dowatch实现的,它们都是一个effect;只不过数据变化之后需要执行watch的回调并且把新旧值传递进去,而对于watchEffect不用执行回调直接执行effect的run;
18. 如何将template转换成render函数?
- 通过正则匹配开始结束标签,匹配属性,指令,事件,内容,注释等,通过字符串截取的方式不断的截取;生成相应的ast抽象语法树;
- 根据抽象语法树进行静态节点和静态树的标记;
- 根据节点,属性和内容分别使用对应的生成虚拟节点的函数进行包裹,放在with中包裹成字符串,通过new Function可以执行这些字符串代码,最后返回一个render函数;
19. new Vue的过程做了什么?
- 合并options对属性进行统一的格式化处理,确定父子关系,给当前实例上定义一些内部属性;添加一些事件;设置
$attrs和$listeners; - 执行beforeCreate钩子
- 初始化inject;初始化props,methods,computed,watch和data,data的数据设置为响应的;初始化provide;
- 执行created钩子;
- 执行$mount进行模板编译和挂载
- 没有获取到render函数就使用template模板,如果也没有就使用el元素自身进行编译生成render函数;
- 执行beforeMount钩子
- 创建渲染watcher,执行render函数进行依赖收集,通过patch进行dom的创建和挂载;
- 执行mounted钩子
20. vue.observable用来做什么?
vue2中的方法,此方法用于把普通对象变为响应式的对象;vue3中把响应式单独提出到一个模块中并且暴露出来给用户使用,比如ref,reactive;
21. v-if和v-for的优先级?
vue2中的v-for的优先级高于v-if;vue3中的v-if优先级高于v-for,它内部会把v-if提取到外层通过template包裹;不建议它们一起使用,可以通过计算属性进行处理再遍历;
22. vue中生命周期有哪些?一般在哪个钩子进行请求数据?
vue中的生命周期有:实例的创建前后,dom的挂载前后,组件的更新前后和组件的销毁前后;
breforeCreate:在此之前初始化了组件的绑定事件,确定了组件的父子关系;
created: 在此进行了数据的初始化,比如provide,inject,props,methods,watch等,把data中的数据设置为响应的;实例也创建完成,可以使用实例和属性了;
beforeMount: 模板编译完成已经生成了render函数;
mounted: 模板挂载完成可以操作dom;
beforeUpdate: 组件更新之前;
updated: 组件更新之后;
beforeDestroy: 组件销毁之前;
destroy: 组件销毁,实例销毁,所有的事件都被解绑;
activated: keep-alive组件激活的时候;
deActivated: keep-alive组件失活的时候;
vue3中把destory改为了unmounted,beforeDestory改成了beforeUnmount;新增了setup方法,此方法在breforeCreate之前执行,并且实例已经创建完成可以获取到组件的实例;
24. vue中key的作用和原理?你对它的理解?
key的主要作用就是用来判断当前节点是不是同一个dom;vue中采用就地更新原则,如果一个元素没有使用key并且它是动态变化的,比如使用了v-if,那么再次显示的时候直接复用之前的dom,只是更新属性或内容的不同;如果使用了key,动态变化之后的key不同就会删除dom重新创建;如果一个列表中没有使用key就会出问题;比如在一个列表中的头部插入一个节点,那么它不会在头部创建节点,而是复用之前的dom把内容改了,在列表的尾部创建一个节点;因此对于列表一定要有key,这样可以在diff算法的时候正确的比对出变化的节点进行更新;并且不能使用索引作为key,因为每次遍历key都是从0开始,不是一个唯一的标识;
25. vue.use是用来干啥的?
主要是用来给vue添加一些插件,可以传递一个对象,对象中必须要有install属性,也可以传递一个函数,内部会把函数作为install方法;插件其实就是一个函数,内部调用这个函数把vue传递进去,并且会对插件进行缓存,再次使用就会从缓存中获取返回;
26. vue.extend方法的作用?
用来创建一个子组件,它内部创建了一个子类。通过原型继承的方式继承父类,并且把父类上的一些属性和方法存到了自己身上,并且对子类进行了缓存,一开始先从缓存中获取;最后返回这个子类; 可以手动创建一个子组件,挂载到指定的元素中;全局组件就可以通过该方法创建;但是使用该方法创建的组件需要进行模板编译,因此需要引入模板编译的包,整个项目体积就会增加;消耗一定的性能;
27. vue的data属性为什么必须是一个函数?
根组件上的data可以是对象也可以是函数,而其他组件必须是函数;因为根组件只会被使用一次,而其他组件可能被多次使用,每次使用都会创建组件的实例,把data作为实例上的一个属性,多个实例同时使用一个data就会造成数据共享;如果是一个函数,每个组件在创建的时候都会执行这个函数把返回的结果作为实例上的一个属性,那么每个组件都有自己的data属性,就不会存在data共享的问题了;
28. 函数式组件的优势?
vue2中函数式组件比实例创建的组件性能要好一些,因为函数式组件不用通过new构造函数的形式创建,它没有this,没有生命周期,无状态的;它直接返回一个虚拟节点进行渲染;
29. vue中的过滤器和应用场景?
vue2中的过滤器其实就是一个函数,vue内部把用户传递的参数放入到用户传递的函数中执行;应用场景:单位的转换,时间格式等;vue3中已经废弃,因为它只是一个函数,用户就可以实现此功能;
30. v-once的使用场景?
v-once是vue的内置指令,带有v-once的组件或元素只会在初始渲染的时候渲染一次;后续不管里面的内容或数据变化都不会再次渲染;它内部把第一次生成的虚拟节点进行了缓存并且标记为静态的,下次渲染的时候直接跳过,使用之前的结果;一般用于更新优化;v-once只影响该节点的渲染,不会影响子节点;如果子节点的数据变化了子节点会进行重新渲染;
31. vue.mixin的原理和使用场景?
原理:mixin采用了策略模式对传入的对象进行合并,对于props,methods,inject,computed进行了合并,后代中的同名会进行覆盖混入;对于data会把mixin中存在的组件中不存在的属性进行添加到当前组件的data中;对于生命周期和watch会合并为一个数组;对于components,指令和过滤器通过原型继承;
场景:抽离一些公共的逻辑,可以进行组件的扩展;mixin的缺点会有命名冲突和数据来源不明确;vue3中使用组合式api可以实现这个功能;
32. vue中slot的实现和使用场景?
场景: 方便对组件进行扩展,可以使用具名插槽插入到指定的位置,可以通过作用域插槽传递动态的数据;可以封装弹框组件,提示组件等;
原理:slot其实是用来占位的,在父级渲染的时候就slot就渲染完成,对于普通插槽在子组件创建的时候直接替换到相应的位置;对于作用域插槽,在子级渲染的时候会生成一个key和对应的函数,函数内部返回子组件的render函数,当子组件创建的时候会通过插槽的名称key找到对应的函数,并且把父级数据传递给这个函数执行,子组件的render函数中就可以使用这个数据,从而创建对应的内容插入到指定的地方;
33. 说说你对双向绑定的原理和理解?
vue中的双向绑定是通过v-model实现的,数据修改视图更新,视图修改数据,数据同步更新;v-model只能用在一些表单元素和组件上;vue2中一个元素上只能用一次v-model;而vue3中一个元素上可以使用多个v-model,因为vue3中可以指定不同的属性名和事件名来区分不同的v-model;
原理:v-model用在不同的表单元素上,实现的原理也不同;比如用在input上,它就是通过绑定一个value属性和input事件,用户输入的时候会触发input事件,input事件中获取到新值赋值给value绑定的数据,并且对于中文有延迟的处理;对于checkbox是通过绑定checked属性和change事件;vue3和vue2中的v-model实现原理相同,只不过vue3中v-model默认的属性名为modelValue,事件名为onUpdate:modelValue;
34. vue中.sync修饰符的作用?
通过.sync修饰的属性,子组件可以通过emit一个update:属性名来直接修改父组件中的这个属性,不用在父组件中监听对应修改的事件;
原理:父组件中默认会生成一个update:属性名的事件,事件中直接修改属性;
作用:用在组件上,可以当作v-model一样使用,如果一个组件上v-model满足不了,就可以使用sync来实现达到使用多个v-model的功能;vue3中已经废弃,因为vue3的v-model可以指定属性名实现一个元素上使用多个v-model;
35. vue中是怎么实现递归组件
模板语法中:给递归的组件指定name名称,通过name名称实现递归; 在jsx语法,在render函数中递归组件;
36. 组件中指定name的作用?
- 通过name方便找到对应的组件;
- 递归组件的时候通过name获取到当前组件
- keep-alive中通过name来缓存或不缓存对应的组件
- $children中通过name可以找到对应的组件
- 便于动态组件的调用
- 方便调试,使用vue调试工具的时候可以通过name很快识别出对应的组件;
37. vue中常见的修饰符有哪些和应用场景?
- 表单修饰符:trim去除两边空格;number只能输入数字;
- 事件修饰符:stop阻止冒泡;prevent阻止默认事件;native在原生的元素上执行此事件(vue3已废弃);
- 鼠标修饰符:left,right;
- 键盘修饰符:enter等
- .sync修饰符:子组件直接修改父组件上的数据(vue3已废弃);
38. vue中异步组件的作用和应用?
异步组件主要用于对大组件进行分离加载,组件比较大会影响页面的加载,因此通过异步加载组件的方式引入;配合webpack的code-splitting可以进行分包打包;
原理:vue2中的异步组件,内部分别定义了resolve和reject方法;当异步组件加载完成之后就会执行promise的resolve方法;在resolve方法中调用extend方法创建此组件的构造器并且会通过$foreUpdate进行强制更新此组件;如果异步组件没有加载完成就创建一个空的注释节点进行占位;
vue3中异步组件默认通过碎片节点进行占位,通过响应式数据并且判断是否有加载组件,错误组件,设置了超时,如果有加载组件就渲染加载组件,如果组件加载错误或者超时就渲染错误组件,组件加载完成会执行promise的resolve方法,把加载完成的响应式数据设置为true,就会重新执行effect渲染对应的组件;
39. vue中组件的原理?
vue3:在渲染的时候如果是组件就创建组件的实例,处理组件的props和slots,把子元素作为slots属性值,有setup方法就执行setup方法,如果setup方法返回的是一个函数,那么就作为render方法,否则就是一个对象,进行代理这个对象,判断对象中的值是不是ref,是就直接返回.value的值;创建组件的effect,组件的scheduler通过promise异步更新;effect中执行组件的render函数生成对应的虚拟dom;通过patch方法创建挂载真实的dom;
vue2:在模板编译的时候,根据ast生成对应的虚拟节点,如果是组件就调用父级的extend方法创建子组件的构造器;并且合并一些钩子,此时有个init钩子,init钩子内部执行构造器创建组件的实例并且调用$mount进行挂载;在挂载的时候,判断如果当前虚拟dom是不是组件,如果是就执行实例上的init钩子,创建组件的实例,调用$mount进行创建挂载到对应的位置;
40. 说说你对nextTick的理解和原理?
vue中dom的更新是异步的,nextTick主要用来在dom异步更新之后执行一个回调从而可以做一些操作;vue中视图的更新也是通过nextTick异步更新的;vue2中的nextTick中通过一个数组来存储当前数据对应的依赖和用户传递的回调函数;通过降级的形式由promise,mutationObserve,setImmdate到setTimeout,来遍历这个数组执行每个依赖或回调;最后返回一个promise;如果nextTick写在修改数据的前面,那么nextTick的回调会先被push到数组中,修改数据的依赖再被push到数组中,因此异步执行的时候先执行nextTick的回调再执行依赖更新视图;所以nextTick需要放在修改数据的后面;
vue3中的nextTick直接通过一个promise来异步执行回调,并且返回这个promise;
41. 自定义指令的使用场景?
指令其实就是一个dom从创建到销毁的整个过程中注入一些钩子函数;通过这些钩子来操作dom做一些事情;
指令的生命周期有bind, insert, update, componentUpdate, unbind;vue3中对指令的生命周期有所修改,分别是create:元素已经创建但是没有添加属性和事件,beforeMount:元素被插入到文档之前,mounted:插入到页面中,beforeUpdate父组件更新之前,updated父组件更新之后,beforeUnmount父组件卸载之前,unmounted父组件卸载之后;
应用场景:图片懒加载,防抖,点击事件的处理等;
42. vue中使用了哪些设计模式?
- 工厂模式:比如createElement函数;
- 发布订阅者模式:watcher和Dep和数据之间的关系;
- 代理模式:当访问实例上的属性的时候内部代理了_data对象;
- 策略模式:合并属性的时候,通过策略模式来判读是props还是methods等; 等
43. vue中性能优化有哪些?
- data中的数据不要太深,因为数据的初始化通过递归的形式给数据添加getter和setter;
- 可以通过Object.freeze冻结后续不需要响应式的数据
- 使用数据中唯一的属性作为key,不要使用索引;
- 合理使用v-if和v-show
- 合理拆分组件
- 合理采用异步组件或路由懒加载,通过打包工具可以分包
- 使用keep-alive进行缓存组件
44. 首屏加载慢怎么解决?
- 通过路由懒加载或异步组件进行分包,减少包的体积;
- 采用骨架屏
- 抽离公共的代码
- 静态资源通过浏览器进行缓存;
- 图片进行压缩,小图片通过base64进行加载;
- 打包的时候开启压缩,打成zip包;
- 采用服务端渲染
45. vue中怎么实现权限管理?控制到按钮级别的权限怎么处理?
- 页面和菜单的权限:页面可以通过接口返回当前用户的所有有权限的页面路由,动态路由的添加这些路由;菜单权限,直接通过接口返回当前用户的菜单数据进行渲染;
- 按钮权限:通过自定义指令,把自定义指令绑定到需要权限的按钮上,在指令的insert钩子中判断当前用户是否有权限,没有就移除这个dom;
自定义权限指令
eg:
v-has: {
insert(el, bindings, vnode){
const value = bindings.value
const permissions = vnode.context.$store.state.user.btnPermissions
if (!permissions.includes(value)) {
el.parentNode.removeChild(el)
}
}
}
46. vue-router有哪些钩子?执行的时机?
全局守卫:beforeEach,beforeResolve,afterEach;
路由独享守卫:beforeEnter;
组件内守卫:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave;
- 在离开路由之前执行组件内守卫beforeRouteLeave钩子;
- 在路由跳转之前执行全局的beforeEach守卫,用于全局的路由拦截和权限验证;
- 在进入路由之前执行路由独享守卫beforeEnter
- 解析异步路由组件
- 在进入路由之前执行组件内守卫beforeRouteEnter
- 路由解析完成执行全局解析守卫beforeResolve
- 路由跳转完成之后执行全局后置守卫afterEach
- dom更新完成之后执行beforeRouteEnter中的next方法
47. vue-router有几种模式?它们的区别?
三种模式分别为hash,history,abstract;
- hash模式:通过hashChange或popState监听hash的变化从而渲染对应的组件;优点:兼容性好,可以不应用在http协议中;缺点:不美观,不从服务端获取;
- history模式:通过historyAPI和popState来修改监听地址的变化渲染对应的组件;优点:美观;缺点:不能通过本地直接打开,需要服务端处理404;
- abstract:应用在非浏览器环境中
48. 你对vuex的理解?
vuex是一个状态管理工具,集中的存储和管理所有组件的状态,可以实现数据共享;可以通过mutation或Action修改数据,通过state或getter获取数据;
缺点:多模块需要添加namespace进行区分;不够扁平化,深层次的状态需要通过.连续的访问
在window或localstore中不是也可以实现数据共享? 是可以实现,但是数据不是响应式的,可以通过Vue.observe设置为响应式的;
49. 如何监听vuex中数据的变化?
vuex中数据是响应式的,可以通过Watch进行监听,也可以通过Store.subscribe监听
50. vuex中mutation和Action的区别?
mutation只能处理同步逻辑;action可以处理异步逻辑,通过mutation修改state;
51. vuex中的module用来做什么的?
vuex中的module是对状态进行划分的,通过module可以将状态划分为多个模块,每个模块都有自己的state,getters,mutations,actions;用于维护大型应用中的状态;通过namespace进行区分;
52. vue3中的组合式API的优势?
- 组合式API可以更容易的抽离公共的逻辑,不需要向vue2中采用mixin的方式,可能会造成命名冲突和数据来源不明确的问题;
- 按照功能模块进行拆分:组合式API可以将每个功能相关的数据和钩子放在一起;
- 灵活的逻辑组合,多个组合逻辑可以一起组合使用;
- 更好的支持ts;
53. vue3和vue2的区别?
- vue3中提供了组合式API,组合式API相比optionsAPI更加灵活,根据功能模块随意组合,也方便抽离公共逻辑代码;
- vue3中对模块进行了拆分,需要哪个模块就引入哪个模块,相比vue2通过打包插件可以进行抖动掉没有使用的代码,从而减少包的体积;
- vue3提供了自定义渲染器,扩展能力强;
- vue3中的响应式通过proxy进行代理,不需要递归初始化整个属性,并且可以代理Set和Map对象和数组;并且给对象删除和新增属性都能被监听到,对于基本类型vue3创建了一个类,通过setter和getter类的value方法可以实现对象基本类型数据的代理;而vue2中使用Object.defineProperty通过递归给对象上的每个属性添加setter和getter,此方法不能监听到对象新增和删除的属性;
- vue2中diff算法对于乱序中的每个元素都需要移动到前面;而vue3中采用了最长递增子序列算法进行了优化处理;
- vue3中增加了很多新特性,比如teleport传送门组件,Fragment可以不用指定一个根元素,单文件组件中的style支持v-bind动态绑定样式的值等等;
- vu3中对于模板编译也进行了优化,标记动态节点,静态提升,缓存函数等;
54. vue中错误是怎么处理的?
- errorCapture钩子捕获当前组件以及后代组件中的错误,如果回调中没有返回false那么就一直往上传递错误信息,直到最外层组件中;如果有返回false,直接终止向上传递错误;
- vue.config.errorHandler:捕获全局错误信息,如果errorCapture返回false,那么它就捕获不到;
55. vue3中模板编译做了哪些优化
- patchFlags标记动态节点,包括动态的文本,属性等,在diff算法中比对的时候只处理动态节点;
- 静态提升:对于静态的属性或元素在第一次创建之后就提到外部,下次更新的时候直接使用;
- 预字符串化:对于连续超过20个静态节点就通过字符串进行包裹;
- 缓存函数:对于事件这些函数进行缓存,下次更新直接从缓存中获取,不用重新创建;
56. vue3中的新特性有哪些?
- 组合式API;
- teleport传送门组件;
- setup函数;
- script元素上添加setup属性,就不用写setup函数了;
- Fragment不用指定一个根元素;
- createRenderer自定义渲染器,可以实现跨平台渲染;
- 单文件组件的style中可以通过v-bind动态设置样式的值;
57. 模板编译
vue3中的模板编译的核心主要在compiler-core模块中,compiler-dom是操作dom;compiler-sfc是处理单文件组件;compiler-ssr是处理服务端渲染; vue中模板编译主要有三个阶段,分别是parse,transform,codegen;
- parse:通过正则不断匹配字符串中的元素,属性,文本,插槽,注释等;匹配到之后通过相应的函数生成对应的ast抽象语法树;并且截取匹配的字符;不断的匹配截取知道字符串处理完毕;
- transform: 对ast抽象语法树进行优化,标记静态节点,事件缓存,静态提升等;
- codegen: 生成Render函数,render函数中主要是字符串代码,根据ast中不同类型通过不同函数和with进行包裹成字符串的形式;通过new Function就可以执行这些字符串代码,生成对应的虚拟dom;
58. keep-alive的应用场景和原理?
应用场景:keep-alive是vue的一个内置组件,主要用在包裹动态组件或router-view上,这样再次切回的时候就不用重新渲染组件了;
原理:keep-alive主要在组件mount和updata的时候缓存子组件实例;并且在切换组件的时候会创建一个空的元素,把当前组件dom移动到这个空元素中;当再次切回这个组件的时候,从缓存中获取,并且添加一个标识,在更新组件的方法中判断如果有这个标识,就直接获取到这个组件的dom移动到keep-alive的位置;keep-alive组件有三个属性,分别是includes,excludes和max;max用来控制缓存的最大数量,内部采用了LRU最久未使用原则,keep-alive中组件再次激活或失活的时候只会执行activate或deactivated两个钩子;因为它不会重新创建和销毁;
59. teleport的应用场景和原理?
主要用来移动当前组件到指定元素的下面;
原理:在patch的时候如果是teleport组件,就执行teleport的process方法,并且提供挂载子元素,更新子元素,移动子元素和查询元素的方法;在process内部判断是否是创建的元素,如果是就直接调用挂载子元素的方法进行创建并且插入到to属性的元素中;如果是更新就更新新旧元素,并且比较两个元素的to是否一样,如果不一样就查询到这个元素,把新的子元素移动到这个to元素中;