1. 谈谈你对MVVM开发模式的理解?
MVVM是一种简化用户界面的实践驱动编程方式。在当前主流的前后端分离的开发模式中,MVVM模式的优越性日益体现,相较于经典的MVC模式,其对于程序模块的封装很好地解决了前后端信息交互的冗余和繁琐
MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。
为什么使用MVVM:低耦合,可复用,独立开发,可测试
2. v-if 和 v-show 有什么区别?
手段
v-if是动态的向DOM树内添加或者删除DOM元素;
v-show是通过设置DOM元素的display样式属性控制显隐;
编译
v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译;
v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;
性能消耗:
v-if有更高的切换消耗;
v-show有更高的初始渲染消耗
3.r o u t e 和 route和route和router区别
$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数
$router 是“路由实例”想要导航到不同URL 对象包括了路由的跳转方法,钩子函数等。通过push、replace、go、back等方法,来实现页面间的跳转
4.vue自定义指令
vue指令
vue2
局部注册:directive选项
directives: { 'focus': { bind(el, binding, vnode) { el.focus() } } }
全局注册:main.js
Vue.directives('focus',{ bind(el, binding, vnode) { el.focus() } })
生命周期:
bind:只调用一次,指令第一次绑到元素调用,用于初始化
inserted:被绑定元素插入父节点时调用
update:所在组件vnode更新调用
componentUpdate:指令在组件的vnode及子组件的vnode全部更新完调用
ubind:只调用一侧,指令解绑
vue3
局部注册:引入 import { Directive , DirectiveBinding } from ‘vue’ 分别校验vFocus,binding
<template> <input type="text" v-focus="{ color: 'red' }" /> </template> <script setup> const vFocus = { created(el, binding) { el.style.backgroundColor = binding.value.color; console.log(el, binding.value.color); //<input type="text" style="background-color: red;"> 'red' }, }; </script>
全局注册:main.js,app.vue如上引入
import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.directive('focus', { created(el, binding) { el.style.backgroundColor = binding.value.color; console.log(el, binding.value.color); //<input type="text" style="background-color: red;"> 'red' } }) app.mount('#app')
生命周期:
created 元素初始化的时候
beforeMount 指令绑定到元素后调用 只调用一次
mounted 元素插入父级dom调用
beforeUpdate 元素被更新之前调用
update 这个周期方法被移除 改用updated
beforeUnmount 在元素被移除前调用
unmounted 指令被移除后调用 只调用一次
5.vue项目优化
代码层面
长列表性能优化
事件销毁, beforeDestroy生命周期函数内执行销毁逻辑。
图片懒加载
路由懒加载
按需加载插件
v-if,v-for避免同时使用
v-if,v-show选择
keep-alive组件缓存
input防抖节流
基础的web技术优化
开启gzip压缩
浏览器缓存
CDN加速
webpack优化
6.vue模板如何编译
Vue的模板编译就是将“HTML”模板编译成render函数的过程。这个过程大致可以分成三个阶段:
解析阶段:将“HTML”模板解析成AST语法树;
核心 parseHTML( template ,{}) Vue定义了很多匹配HTML的正则表达式 ,parseHTML根据正则匹配
parseHTML是解析模板字符串的“主线程”,它的第一个参数是要解析的模板字符串, 也就是单文件组件中最外层 所包裹的部分;第二个参数是一个选项对象,它会包含一些回调,以及一些配置项。
选项对象:
start( tag, attrs, unary ) 匹配到开始标签时的回调,tag为当前标签的标签名,attrs为该标签上的属性列表,unary为当前标签是否为自闭合标签
end() 匹配到结束标签时的回调
chars(text) 匹配到文本节点的回调
comment(text) 匹配到注释节点的回调,其处理逻辑跟文本的处理逻辑类似
优化阶段:从AST语法树中找出静态子树并进行标记(被标记的静态子树在虚拟dom比对时会被忽略,从而提高虚拟dom比对的性能);
上面简单介绍过,优化阶段的工作就是标记静态子树,标记静态子树后主要有以下两个优点:
生成虚拟dom的过程中,如果发现一个节点是静态子树,除了首次渲染外不会生成新的子节点树,而是拷贝已存在的静态子树;
比对虚拟dom的过程中,如果发现当前节点是静态子树,则直接跳过,不需要进行比对。
标记静态子树的过程分为两个步骤:
遍历AST语法树,找出所有的静态节点并打上标记(注:当前节点及其所有子节点都是静态节点,当前节点才会被打上静态节点的标记)
遍历经过上面步骤后的树,找出静态根节点,并打上标记(注:静态根节点是指本身及所有子节点都是静态节点,但是父节点为动态节点的节点,找到了静态根节点也就找到了“静态子树”)
代码生成阶段:通过AST生成代码字符串,并最终生成render函数。
7.vue2响应式原理
vue 采用了几个核心部件 : Observer ,Dep, Watcher ,Scheduler
observer把一个普通的对象转换成响应式的对象
observer 把对象的每个属性通过 object.defineProperty 转换为带有 getter 和 setter 的属性
Dep 表示依赖, vue 会为响应式对象中的每一个属性,对象本身,数组本身创建一个 dep 实例,每个 dep 实例都可以做两件事情 :
记录依赖:是谁在用我
派发更新:我变了,我要通知那些用我的人
watcher 在函数执行的过程中,如果发生了依赖记录,那么 dep 就会把这个全局变量记录下来,表示有一个 wathcer 用到了我这个属性。
Scheduler 不会立即执行更新,通过nexttick异步更新
8.vue3响应式原理
通过Proxy(代理): 拦截对象中任意属性的变化,包括:属性值的读写,属性的增加,属性的删除等。
通过Reffect(反射): 对源对象的属性进行操作, Reflect不是一个函数对象,因此它是不可构造的。
9.刷新浏览器后,Vuex的数据是否存在?如何解决?
不存在
原因: 因为 store 里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值初始化。
我们有两种方法解决该问题:
使用 vuex-along
使用 localStorage 或者 sessionStroage
10.vue和react共同点?区别
共同点:
数据驱动视图
组件化
都使用 Virtual DOM
不同点:
核心思想不同
vue定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。这就有了vue的主要特点:灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确。
react 定位就是提出 UI 开发的新思路 React推崇函数式编程(纯组件),数据不可变以及单向数据流,当然需要双向的地方也可以手动实现, 比如借助onChange和setState来实现。
组件写法
React推荐的做法是JSX + inline style, 也就是把 HTML 和 CSS 全都写进 JavaScript 中
Vue 推荐的做法是 template 的单文件组件格式(简单易懂,从传统前端转过来易于理解),即 html,css,JS 写在同一个文件(vue也支持JSX写法)
diff算法
响应式原理
vue2采用object.defineProperty ,vue3采用proxy,reflect
React基于状态机,手动优化,数据不可变,需要setState驱动新的state替换老的state。
11.vue双向数据绑定原理
简易实现:v-model分为两部分,通过v-bind绑定值,再通过v-on:input来通步修改值
原理:
需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
通过dep来理清依赖关系,watcher在依赖中添加自身
compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
待属性变动dep.notice()通知时,能调动watcher自身的update方法,并处罚compile回调渲染视图
12.computed和watch区别
computed计算属性,watch监听属性
计算属性不在 data 中,它是基于data 或 props 中的数据通过计算得到的一个新值。watch 可以监听的数据来源:data,props,computed内的数据
component中有get和set方法,会默认缓存计算结果。watch不支持缓存,支持异步, immediate监听属性立即执行一次,deep开启深度监听
13.Vuex
Vuex是一种状态管理模式,存在的目的是共享可复用的组件状态。
主要包括以下几个模块:
State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter => 从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。
Action => 像一个装饰器,包裹mutations,使之可以异步。用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
14.vuex辅助函数
mapState, mapMutations, mapActions, mapGetters
mapState和mapGetters:
两者都放在 computed中,以mapState举例
import { mapState } from 'vuex'
import { mapState } from 'vuex' computed中 computed:{ ...mapState(['data']) //data是vuex存放的state中的属性,此时{{data}}可使用 }
mapMutations, mapActions:
放在组件的methods属性中 。使用与上类似
15.vuex模块化使用
当我们开发的项目比较大时,store中的数据就可能比较多,这时我们store中的数据就可能变得臃肿,为了解决这一问题,我们就需要将store模块化(module)
前提:创建两份js文件,含有属性与vuex写法相同,需要通过 namespaced:true开启命名空间store/index.js:在modules中引入文件
使用:
访问state数据:
第一种方式:this.$store.state.moduleA.sum
第二种方式: ...mapState('moduleA',['sum','number'])
action提交mutation
第一种方式:需要传参this.小明,无需传参store.dispatch(′moduleB/addZhang′,name:′小明′,age:18),无需传参this.store.dispatch('moduleB/addZhang',{name:'小明',age:18}) ,无需传参this.store.dispatch('moduleB/addServer')
第二种方式:...mapActions('moduleB',['addZhang'])
getters计算属性
第一种方式: this.$store.getters['moduleB/firstName']
第二种方式:...mapGetters('moduleB',['firstName'])
16.vue中mixin
mixin(混入): 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods 、created、computed等等
我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来
具体使用:
创建mixins.js文件
let mixin = { created() { console.log('我是mixin中的'); }, methods: { hellow() { console.log('你好'); }, }, } export default mixin
局部使用
import mixin from "./mixins"; export default { mixins: [mixin], mounted() { this.hellow();//你好 }, };
全局使用main.js
import { createApp } from 'vue' import App from './App.vue' import mixins from "./mixins"; const app = createApp(App) app.mixin(mixins) app.mount('#app')
17.Vue中给对象添加新属性时,界面不刷新怎么办?
原因:vue2响应式采用object.defineProperty进行劫持,那个添加新属性时,新的属性不会具有get和set方法,不是一个响应式所以界面不刷新
解决:Vue.set() 向响应式对象中添加一个property,并确保这个新 property 同样是响应式的
vue3通过proxy劫持和reflect映射实现响应式,不会有这个问题
18.vue组件通讯方式
通过 props 传递
props校验:name:{type:String,required:true,default:默认值} required是否必要
通过 $emit 触发自定义事件
使用 ref
EventBus
Provide 与 Inject
Vuex
19.vue3setup的父传子怎么去写?
介绍三种方法:
第一种:使用vue2写法通过props和$emit
第二种:setup函数写法
setup(props,context),通过props接收数据,通过context.emit(‘调用父组件方法’,传递参数)
第三种:script中setup
const emits = defineEmits(["changeNumber"]); // 也可以不赋值,取值通过{{num}}获取 const props = defineProps({ num: { type: Number, default: () => [], }, list: { type: Array, }, }); const changeNum = function () { emits("changeNumber", 888); // console.log(11111111111); };
20.setup可不可以直接写async和await?
可以
setup 语法糖中可直接使用 await,不需要写 async , setup 会自动变成 async setup
<script setup> import Api from '../api/Api' const data = await Api.getData() console.log(data) </script>
21.vue生命周期
vue2
beforeCreate – 首次访问data
created – 首次访问this生命周期
mounted – 页面展示
vue3
区别:
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。dszhuoyi
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
22.说说 Vue 中 CSS scoped 的原理
添加scoped标签后会给组件中所有标签元素,添加一个唯一标识,这个唯一标识就是自定义属性,data-v-xxxxxxxx这样的字眼,同时对应的样式选择器也会添加这个唯一的属性选择器
23.$nextTick原理
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
nextTick原理
24.data是函数不是对象
vue是一个单页面应用最终所有的实例都会挂载到app.vue文件,如果data是一个对象那么会导致数据污染。通过函数返回对象的方式,利用函数作用域的限制避免数据污染
25.路由守卫
vue路由守卫分为三种:全局路由守卫,独享路由守卫,组件路由守卫
to: 进入到哪个路由去
from: 来自哪个路由
next:是否跳转
全局守卫: router.beforeEach((to,from,next)=>{})
独享路由守卫: beforeEnter:(to,from,next)=>{}
组件路由守卫: beforeRouteEnter:(to,from,next)=>{}, beforeRouteUpdate , beforeRouteLeave