1.虚拟 DOM 实现原理
虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象状态变更时,记录新树和旧树的差异,最后把差异更新到真正的dom。
2.如何理解 Vue 的响应式系统的
任何一个 Vue Component 都有一个与之对应的 Watcher 实例。Vue 的 data 上的属性会被添加 getter 和 setter 属性。
当 Vue Component render 函数被执行的时候, data 上会被 触碰(touch), 即被读, getter 方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有 data。(这一过程被称为依赖收集)。
data 被改动时(主要是用户操作), 即被写, setter 方法会被调用, 此时 Vue 会去通知所有依赖于此data 的组件去调用他们的 render 函数进行更新。
3.vue-router 有哪些钩子函数?
全局前置守卫 router.beforeEach
全局解析守卫 router.beforeResolve
全局后置钩子 router.afterEach
路由独享的守卫 beforeEnter
组件内的守卫 beforeRouteEnter 、 beforeRouteUpdate 、 beforeRouteLeave
4.route 和 router 的区别是什么?
router为VueRouter的实例,相当于一个全局的路由器对象,里面含有很多属性和子对象,例如history对象. 经常用的跳转链接就可以用this.$router.push,和router-link跳转一样。
route相当于当前正在跳转的路由对象。可以从里面获取name,path,params,query等
5.MVVM与MVC的区别
MVC
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。
其中 View负责页面的显示逻辑,
Model 负责存储页面的业务数据,以及对相应数据的操作。
Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。
MVVM
即Model-View-ViewModel
Model代表数据模型,也可以在model中定义数据修改和操作业务逻辑
View 代表 UI组件,它负责将数据模型转化成UI展现出来
ViewModel监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View和Model的对象,连接Model和View
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
6.Vue 并没有完全遵循 MVVM 思想呢?
严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了 $refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。
7. 说一说Vuex是什么,每个属性是干嘛的,如何使用?
Vuex是为vue开发的状态管理模式,集中存储管理所有组件的状态。有state getters mutations actions module属性。
state用来定义要存储的数据,通过$store.state调用数据。
getters 可以认为是定义 store 的计算属性,通过$store.getters调用。
mutations定义改变state中数据的同步方法,通过$store.commit调用,注:写异步方法ajax,数据就不可跟踪了
action用来存放异步方法,接收的参数是context(mutations中的方法)通过$store.dispatch调用actions中的方法
moudle将store分割成模块,每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,从上至下进行同样方式的分割
使用方法: state :直接以对象方式添加属性
加分回答 可以使用mapState、mapMutations、mapAction、mapGetters一次性获取每个属性下对应的多个方法。 VueX在大型项目中比较常用,非关系组件传递数据比较方便。
8.Vue生命周期运行过程:
(1) new Vue实例诞生
(2)初始化了生命周期和生命周期中的事件
(3)初始化数据
(4)检测是否有el选项
(5)检测是否有template属性选项
(6)no,没有template属性,将el指向的元素的html代码编译成模板
(7)将模板用数据填充,创建出来了vm.el
(8)此时页面已经进入稳定的运行状态了
①此时页面开始监听数据的变化
②当数据发生变化的时候,vue更新页面内容,继续监听
(9)vue实例不会自然死亡。当我们调用vm.$destory方法的时候
(10)卸载所有的数据监听、组件、事件监听
(11)vue的对象销毁
9.Vue中组件的data为什么是一个函数
JS中的实例是通过构造函数来创建的,每个构造函数可以new出很多个实例,那么每个实例都会继承原型上的方法或属性。
vue的data数据其实是vue原型上的属性,数据存在于内存当中,vue为了保证每个实例上的data数据的独立性,规定了必须使用函数,而不是对象。
因为使用对象的话,每个实例(组件)上使用的data数据是相互影响的,这当然就不是我们想要的了。对象是对于内存地址的引用,直接定义个对象的话组件之间都会使用这个对象,这样会造成组件之间数据相互影响。
10.vue中实现数据双向绑定原理
vue数据的双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。其核心就是通过Object.defineProperty()方法设置set和get函数来实现数据的劫持,在数据变化时发布消息给订阅者,触发相应的监听回调。也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变
11.Vue2的局限性
(1)组件逻辑膨胀导致的可读性变差
(2)无法跨组件重用代码
(3)Vue2对TS的支持有限
12.代码重用方法PK
Vue2中的跨组件重用代码,我们大概会有四个选择:
(1) Mixin - 混入:
代码混入其实就是设计模式中的混合模式,缺点也非常明显。
可以理解为多重继承,简单的说就是一个人如何有两个父亲
❌无法避免属性名冲突 - 长鼻子随谁
❌继承关系不清晰
(2) Mixin Factory - 混入工厂
✅代码重用方便
✅继承关系清洗
(3)ScopeSlots - 作用域插槽
❌可读性不高
❌配置复杂 - 需要再模板中进行配置
❌性能低 - 每个插槽相当于一个实例
(4) CompositionApi - 复合API
✅代码量少
✅没有引入新的语法,只是单纯函数
✅异常灵活
✅工具语法提示友好 - 因为是单纯函数所以 很容易实现语法提示、自动补偿
13.使用CompositionAPI理由
✅更好的Typescript支持
✅在复杂功能组件中可以实现根据特性组织代码 - 代码内聚性👍 比如: 排序和搜索逻辑内聚
✅组件间代码复用
14.setup是什么
在以下方法前执行:
Components
Props
Data
Methods
Computed Properties
Lifecycle methods
可以不再使用难于理解的this
有两个可选参数
props - 属性 (响应式对象 且 可以监听(watch))
context 上下文对象 - 用于代替以前的this方法可以访问的属性
15. ref是什么
对基本数据类型数据进行装箱操作使得成为一个响应式对象,可以跟踪数据变化。
16.LifecycleHooks-生命周期钩子
Vue2 Vue3
beforeCreate ❌setup(替代)
created ❌setup(替代)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestory onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured
onRenderTracked
onRenderTriggered
17.说一下怎么把类数组转换为数组
(1)通过call调用数组的slice方法来实现转换
Array.prototype.slice.call(arrayLike)
(2)通过call调用数组的splice方法来实现转换
Array.prototype.splice.call(arrayLike,0)
(3)通过apply调用数组的concat方法来实现转换
Array.prototype.concat.apply([],arrayLike)
(4)通过Array.from方法来实现转换
Array.from(arrayLike)
18.Vue的父子组件生命周期钩子函数执行顺序
(1)加载渲染过
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
(2)子组件更新过程
父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated
(3)父组件跟新过程
父beforeUpdate -> 父updated
(4)销毁过程
父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed
19.说一下Vue3.0你了解多少?
(1)vue3的优点
体积小,速度快,语言使用ts重构,响应式原理改变proxy,diff算法,composition API
(2)双向数据绑定
vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty() 对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。
vue3 中使⽤了 es6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽实现对数据的监控。
这⾥是相⽐于vue2版本,使⽤proxy的优势如下
1.defineProperty只能监听某个属性,不能对全对象监听。可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
2.可以监听数组,不⽤再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。
另外vue3还新增了⼀些内置组件和⽅法,⽐如vue3可以默认进⾏懒观察,使⽤Function-based API,setup函数,对与插件或对象的⼀个按需引⼊,Computed Value ,新加⼊了 TypeScript 以及 PWA 的⽀持等等…
(3)vue3.0按需引⼊
Vue2.x中new出的实例对象,所有的东西都在这个vue对象上,这样其实⽆论你⽤到还是没⽤到,都会跑⼀遍,这样不仅提⾼了性能消耗,也⽆疑增加了⽤户加载时间。
⽽vue3.0中可以⽤ES module imports按需引⼊,如:keep-alive内置组件、v-model指令,等等,不仅我们开发起来更加的便捷,减少了内存消耗,也同时减少了⽤户加载时间,优化⽤户体验。
(4)创建vue2和vue3项目的文件发生的变化
createApp(App).mount('#app') = createApp(根组件).mount('public/index.html中的div容器')
vue2.0中是直接创建了一个vue实例
vue3.0中按需导出了一个createApp (ceateApp做了什么)
vue3中的app单文件不再强制要求必须有根元素。也就是说 在vue2.0中必须要有一个根元素,在vue3中没这个要求
(5)数据和方法的定义
Vue2使⽤的是选项类型API(Options API),Vue3使⽤的是合成型API(Composition API)
Vue2:
data() { return {}; }, methods:{ }
复制代码
Vue3:
数据和⽅法都定义在setup中,并统⼀进⾏return{}
(6)生命周期
组件创建之前:beforeCreate | setup
组件创建完成:created | setup
组件挂载之前:beforeMount | onBeforeMount
组件挂载完成:mounted | onMounted
数据更新,虚拟DOM打补丁之前:beforeUpdate | onBeforeUpdate
数据更新,虚拟DOM渲染完成:updated | onUpdated
组件销毁之前:beforeDestroy | onBeforeUnmount
组件销毁后:destroyed | onUnmounted
(7)获取props
vue2:console.log(‘props’,this.xxx)
vue3:setup(props,context){ console.log(‘props’,props) }
(8)给父组件传值emit
vue2:this.$emit()
vue3:setup(props,context){context.emit()}
20.axios的封装与axios带拦截器的封装api中代码的不同
(1)axios是什么
1:axios是一个基于http的客户端,在浏览器和node.js两个环境中运行
2:浏览器中可以向服务端发送ajax请求,来获取数据。
3:node.js中借助ajax向远端发送http请求
(2)导入的文件不同
axios不带拦截器的封装 需要引入axios ( import axios from “axios” )
而带拦截器的则不需要 引入axios ,只需要引入 封装好的拦截器的js文件(import service from “@/util/service.js”)