前端面试自用总结--vue

109 阅读15分钟

vue

MVC和MVVM
 MVC是Model-View-Controller的简写,Model就是模型,对应后端数据,View就是视图对应用户界面,Controller就是控制器,对应页面的业务逻辑。MVC的工作机制原理就是,用户操作会请求服务器路由,路由就会调用对应的控制器来处理,控制器就会获取后台数据,将结果返回给前端,进行页面渲染。
 MVVM是Model-View-ViewModel的简写。它本质上就是MVC的改进版,M和C是一样的,ViewModel的存在目的是抽离Controller中展示的业务逻辑,其它的业务逻辑还是在控制器中,整体和MVC差不多。
 最大的区别就是:
 第一,MVC是单向的,而MVVM是双向的,并且是自动的,也就是数据发生变化自动同步视图,视图发生变化自动同步数据。
 第二个,解决了MVC中大量的DOM操作使页面渲染性能降低,加载速度变慢,影响用户体验等问题。
 第三个,在数据频繁更新的时候,采用了虚拟DOM,减少过度渲染,提高性能。
VUE中组件之间如何通信
 props/$emit 父子组件通信
 父->子props,子->父$on$emit 
 (父子间显示调用)父组件通过$refs组件名来获得子组件,子组件通过$parent获得父组件,这样也可以实现通信。
 使用provide/inject在父组件中通过provide提供变量,在子组件中通过inject来将变量注入到组件中。不论子组件有多深只要调用了inject那么就可以注入provide中的数据。
  
 兄弟组件通信
 Event Bus实现跨组件通信,eventBus平级组件数据传递,建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。Vue.prototype.$bus = new Vue()自定义事件
 通过$parent/$refs来获取到兄弟组件,也可以进行通信。
 ​
 vuex
props和data的优先级
 props>methods>data>computed>watch(通过vue源码分析得)                                       
vue生命周期
 var app=new Vue({})表示开始创建一个vue的实例对象,只有默认的一些生命周期函数和默认事件,其他东西都是未创建
 create阶段:vue实例被创建
 beforeCreate: 实例完全被创建出来之前,此时data和methods中的数据都还没有初始化
 created:创建完毕,data和methods已经被初始化好,如果想要操作methods或data最早只能在created中,未挂载(ajax请求最好放在created里面)
 ​
 mount阶段:vue实例被挂载到真实DOM节点
 beforeMount:表示模板已经在内存中编辑完成,但尚未把模板渲染到页面,页面中的元素还没有真正被替换,只是之前的一些模板字符串,可以发起服务端请求,去数据
 mounted: 表示内存中的模板已经真实的挂载到页面中,用户可以看到渲染好的页面,是实例创建阶段的最后一个生命周期函数(只会执行一次),此时可以操作DOM。
 ​
 update阶段:当vue实例里面的data数据变化时,触发组件的重新渲染(运行阶段中的两个事件),先根据data中的最新的数据在内存中重新渲染出一份最新的dom树,会把最新的dom树重新渲染到真实的页面中,这时就完成了数据从data(molde层)到view(视图层)的更新
 beforeUpdate :更新前,页面中的显示的数据还是旧的,此时data数据是最新的,页面尚未与最新数据同步
 updated:更新后,页面和data数据已经保持同步,都是最新的
 ​
 destroy阶段:vue实例被销毁
 beforeDestroy:实例被销毁前,进入到了销毁阶段,此时可以手动销毁一些方法,都还处于可用状态,没有真正执行销毁
 destroyed:销毁后,组件中所有的数据、方法、指令、过滤器等都已经不可用了
 ​
 生命周期(父子组件) 父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件 mounted --> 父组件mounted -->父组件beforeUpdate -->子组件beforeDestroy--> 子组件destroyed --> 父组件updated
双向绑定实现原理
 当一个Vue实例创建时,Vue会遍历data选项的属性,用Object.defineProperty将它们转为getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher重新计算,从而致使它关联的组件得以更新。
 ​
 具体就三个步骤
 1.实现一个监听器0bserver,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
 2.实现一个订阅者watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
 3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,根据初始化模板数据。
vue-router hash路由和history路由实现原理
 1. hash模式
 简介: hash模式是开发中默认的模式,它的URL带着一个#。单单改变 # 后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 # 是用来指导浏览器动作的,对服务器端完全无用。hash 模式通过锚点值的改变,根据不同的值,渲染指定 DOM 位置的不同数据。#符号本身以及它后面的字符称之为hash,可通过window.location.hash属性读取。
 特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
 原理: hash模式的主要原理就是onhashchange()事件:
 window.onhashchange = function(event){
     console.log(event.oldURL, event.newURL);
     let hash = location.hash.slice(1);
 }
 ​
 2. history模式
 简介: history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
 特点: 当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
 API: history api可以分为两大部分,切换历史状态和修改历史状态:
 修改历史状态:包括了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。
 切换历史状态: 包括forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作。
 虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。
 如果想要切换到history模式,就要进行以下配置(后端也要进行配置):
 const router = new VueRouter({
   mode: 'history',
   routes: [...]
 })
vue-router导航守卫
 有的时候需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。
 全局前置/钩子:beforeEach(全局前置守卫 进入路由之前)、beforeResolve(全局解析守卫在beforeRouteEnter调用之后调用)、afterEach(全局后置钩子,进入路由之后)
 路由独享的守卫:beforeEnter
 组件内的守卫:beforeRouteEnter(进入路由前)、beforeRouteUpdate(路由复用同一个组件时)、beforeRouteLeave(离开当前路由时)
keep-alive
 keep-alive是一个缓存组件,它的作用就是避免组件内的数据重复渲染,直接在页面中调用。
 优点:组件切换的时候,组件被保存到了内存中,防止重复渲染减少加载时间,提高运行效率。
 缺点:如果遇到二次路由访问页面,需要使用到路由守卫(before-routerleave),把二级路由保存起来即可。
 keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。
diff算法
 diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新真实Dom。简单来说,Diff算法就是在虚拟DOM树从上至下进行同层比对,如果上层已经不同了,那么下面的DOM全部重新渲染。这样的好处是算法简单,减少比对次数,加快算法完成速度。
 平级比较,不考虑跨级比较的情况。内部采用深度递归的方式+双指针方式比较
 它有两个特点:
 1、比较只会在同层级进行,不会跨层级比较
 2、在Diff比较的过程中,循环从两边向中间比较
 Diff算法步骤:
 (1)用JavaScript对象结构表示DOM树的结构;然后用这个树构建一个真正的DOM树,插到文档当中
 (2)当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异
 (3)把第二棵树所记录的差异应用到第一棵树所构建的真正的DOM树.上(patch),, 视图就更新了
computed和watch的区别
 computed是计算属性,watch是监听属性。它们的共同点都是用来监听数据的变化。
 深度监听:当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,此时需要deep属性对对象进行深度监听deep: true
 它们有两个区别:
 1、是否有缓存功能,computed是有缓存的,只有它依赖的属性值改变的时候,它才会进行计算。而watch是没有缓存功能的,只要监听的数据变化了,它就会触发相应的操作。
 2、是否支持异步,computed是不支持异步的,当computed内有异步操作的时候,它是监听不到数据变化的。watch是支持异步操作的,适合监听路由和设置计时器等。
v-if和v-show的区别
 手段:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐;
 编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
 编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;
 性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换。
v-for中key的作用
 key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点;
 Vue在patch过程中判断两个节点是否是相同节点,key是一个必要条件,渲染一组列表时,key往往是唯一标识,所以如果不定义key的话,Vue只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能;
 从源码中可以知道,Vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。
vuex的使用
 Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。
 1.Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新 
 2. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation, 这样使得我们可以方便地跟踪每一个状态的变化 Vuex主要包括以下几个核心模块:
 1.State:定义了应用的状态数据
 2.Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来, 且只有当它的依赖值发生了改变才会被重新计算 
 3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数 
 4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作 
 5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中
如何简单实现vuex
 创建新的插件
 创建vue的插件
 挂载$store
 实现响应式保存state数据
 实现commit方法
 实现dispatch方法
 实现getters派生状态
$nextTick 的原理和使用的场景
 1.使用原理
 1) vue是异步执行dom更新的,一旦观察到数据变化,vue就会开启一个队列,然后把在同一事件循环当中观察到数据变化的watcher推送进这个队列,如果这个watcher被触发多次,只会被推送到队列一次,这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和dom操作,这样可以提高渲染效率。
 2) 如果要获取更新后的dom元素,可以使用vue内置的$nextTick方法,参数是一个函数。它的作用类似setTimeout,进行执行异步的操作。
 2.应用
 vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick可以获取数据更新后最新dom的变化。
 3.场景:
 1)第三方插件,在vue生成的某些dom动态发生变化时重新应用该插件。
 2)视图更新之后,基于新的视图进行操作。
 ​
 nextTick是一个微任务。
 nextTick中的回调是在下次Dom更新循环结束之后执行的延迟回调
 可以用于获取更新后的Dom
 Vue中的数据更新是异步的,使用nextTick可以保证用户定义的逻辑在更新之后执行
vue3的新特性
 性能提升
 更小巧、更快速
 支持自定义渲染器
 支持摇树优化:一种在打包时去除无用代码的优化手段
 支持Fragments和跨组件渲染
 ​
 API变动
 模板语法99%保持不变
 原生支持基于class的组件,并且无需借助任何编译及各种stage阶段的特性
 在设计时也考虑TypeScript的类型推断特性
 重写虚拟DOM可以期待更多的编译时提示来减少运行时的开销
 优化插槽生成可以单独渲染父组件和子组件
 静态树提升降低渲染成本
 基于Proxy的观察者机制节省内存开销
 ​
 不兼容IE11
 检测机制更加全面、精准、高效,更具可调试式的响应跟踪
vue和react区别
 数据流:
 react主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,
 vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
 ​
 监听数据变化实现原理:
 Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
 React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM的重新渲染。
 组件通信的区别:jsx和.vue模板。
 HoC和Mixins(在Vue中我们组合不同功能的方式是通过Mixin,而在React中我们通过HoC(高阶组件))。
 ​
 性能优化
 React: shouldComponentUpdate
 Vue:内部实现shouldComponentUpdate的优化,由于依赖追踪系统存在,通过watcher判断是否需要重新渲染(当一个页面数据量较大时,Vue的性能较差,造成页面卡顿,所以一般数据比较大的项目倾向使用React)。
vue性能优化
 数据层级不要过深,合理的设置响应式数据
 使用数据时,缓存值的结果,不频繁取值
 合理设置key
 v-show(频繁切换性能高)和v-if的合理使用
 控制组件的粒度 -> Vue采用组件级别更新
 采用函数式组件 -> 函数式组价开销低
 采用异步组件 -> 借助webpack的分包策略
 使用keep-alive来缓存组件
 虚拟滚动、时间分片等策略
 打包优化