vue2和vue3区别
vue3有更好的ts支持,打包大小比vue2减少41%,初次渲染和整体渲染快
双向绑定原理不同(响应式原理)
-
Vue2.0实现响应式(双向数据绑定)的原理是通过 Object.defineProperty 来劫持各个属性的getter、setter,在数据变动时发布消息给订阅者,触发相应的监听回调
- 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式('push','pop','shift'等方法)。
- Object.defineProperty 无法检测到对象属性的添加和删除 。
- 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式(核心API Object.defineProperty)。
- 深度监听需要一次性递归,对性能影响比较大
function observer(target){ if(typeof target !== 'object' || target === null)return target // 如果是数组类型,重写数组原型的方法("push","pop","shift","unshift","splice") if(Array.isArray(target)) target.__proto__ == arrProto; // 如果是对象,遍历对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter for(let key in target){ defineReactive(target,key,target[key]) } }
-
vue3用proxy+Reflect对数据进行处理监听整个对象的变化
- 解决了数组无法通过下标修改,无法监听到对象属性的新增和删除的问题。也提升了响应式的效率
- 基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。
- 不需要一次性遍历data的属性,可以显著提高性能
深入回答:vue3并不是完全抛弃了defineProperty,通过reactive定义的响应式数据使用proxy包装出来,而ref还是用的defineProperty去给一个空对象,定义了一个value属性来做的响应式
数据响应式实现方式不同
- vue2数据写在data中就可以实现响应式
- vue3用ref,reactive实现
ref,reactive
-
ref通过Object.defineProperty()的get和set方法实现数据代理
-
reactive是使用ES6的Proxy对象来实现数据代理
-
ref定义的数据可以是基本数据类型也可以是引用数据类型,、返回一个包含value属性的对象,通过.value去访问具体的值
-
reactive一定义的数据只能是对象或数组或者像map,set这样的集合类型,、返回一个响应式的Proxy对象、可以直接去访问对象的一些属性
一般定义对象和数组用ref还是reactive是根据赋值方式决定的:
-
直接赋值用ref(赋值用reactive的话,会让数据失去响应式proxy{})
-
修改数据用reactive,和ref没区别,ref的话会多个.value
- ref得到的变量必须.value赋值,不然等于把ref变成了普通的数据,失去响应式
- ref的值如果是对象,里面的对象是响应式的,因为引用类型会先包装成proxy再赋值,所以ref的值如果是对象,可以修改其中的属性而引发响应式
-
- 使用
ref()函数可以替换整个对象实例,但是在使用reactive()函数时就不行:
- 使用
// 无效 - x 的更改不会被 Vue 记录
let x = reactive({name: 'John'})
x = reactive({todo: true})
// 有效
const x = ref({name: 'John'})
x.value = {todo: true}
reactive会先判断数据是不是引用类型,是的话就new Proxy,在设置Proxy的get和set,get进行依赖收集,set修改值,触发依赖更新
根结点个数不同
- vue2不支持碎仅div
- vue3引入了碎片Fragments,允许有多个根结点,减少不必要的嵌套
api类型不同
vue2用选项类型api(optionsApi),在代码中分割了不同的属性:dat,methods,computed等,使用生命周期和方法可直接引用
tree-shaking按需引入
-
vue3用组合式api(composition Api)更简洁,方便逻辑更加的聚合使用、使用生命周期和方法需要先引入、可以更好tree-shaking- 组合式api的写法下源码改成了函数式编程、方便按需引入、因为tree-shaking功能必须配合按需引入,所以vue3更好的配合tree-shaking
能打包体积更小 - 因为改成组合式api,所有没有this
- 组合式api的写法下源码改成了函数式编程、方便按需引入、因为tree-shaking功能必须配合按需引入,所以vue3更好的配合tree-shaking
生命周期不同
vue2中:
- beforeCreate: 实例初始化
- created: 实例的数据观测、事件等已经初始化完成
- beforeMount: 相关的render函数首次被调用
- mounted: 挂载后调用,
在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之
注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick
5、beforeUpdate: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
6、updated:组件更新完成后调用,此时虚拟 DOM 已重新渲染并应用补丁
7、activated: 被 keep-alive 缓存的组件激活时调用。
8、deactivated:被 keep-alive 缓存的组件失活时调用。
9、beforeDestroy: 实例销毁之前调用。
10、destroyed: 实例销毁后调用
11、errorCaptured 在捕获一个来自后代组件的错误时被调用
- vue3中:
Vue 3在Composition API中引入了新的生命周期钩子,它们以on开头,并且是在setup()函数中使用的:
setup(): 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 methodonBeforeMount(): 组件挂载到DOM之前调用;onMounted():组件挂载完成后调用,此时可以访问到DOM元素;onBeforeUpdate(): 组件更新之前执行的函数;onUpdated(): 组件更新完成之后执行的函数,虚拟DOM重新渲染和打补丁后调用;onBeforeUnmount(): 组件卸载之前执行的函数;onUnmounted(): 组件卸载完成后执行的函数;onActivated(): 被包含在<keep-alive>中的组件,会多出两个生命周期钩子函数,被激活时执行;onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数
- beforeCreate --> setup(()=>{})
- created --> setup(()=>{})
- beforeMount --> onBeforeMount(()=>{})
- mounted --> onMounted(()=>{})
- beforeUpdate --> onBeforeUpdate(()=>{})
- updated --> onUpdated(()=>{})
- beforeDestroy --> onBeforeUnmount(()=>{})
- destroyed --> onUnmounted(()=>{})
- activated --> onActivated(()=>{})
- deactivated --> onDeactivated(()=>{})
- errorCaptured --> onErrorCaptured(()=>{})
总结: Vue2和Vue3钩子变化不大,beforeCreate 、created 两个钩子被setup()钩子来替代、卸载改成unmount
setUp
- 是一个语法糖,,组件中用到的数据,方法,生命周期都要写在setup中,
- 返回值:如果返回的是对象,对象中的属性和方法可以直接在template中使用、若返回的是一个函数,就可以自定义函数的内容
- 在beforeCreate之前执行一次,是领先所有钩子执行的
- setup的参数,props值为对象,组件外部传递过来,组件内部声明并接受
diff算法不同
v2使用的是双端算法,v3使用的是快速diff算法
静态节点标记PatchFlag
增加了静态节点标记。会标记静态节点,不对静态节点进行比对。从而增加效率
不同动态节点的值时不一样的,这样的目的就是为了区分静态节点以及不同类型的动态节点
深入回答标记策略:在文本节点中文本内容为变量会标记为1,属性为动态会标记为2,如果静态则不标记跳过比对
在vue2中,diff算法执行时,不管你是静态节点还是动态节点,都会进行比较。但是vue3的话就只会比较patchflag标记的节点,跳过了静态节点的比较,大大节省了时间
存放公共数据和生态系统不同
- vue2用vuex和webpack
- vue3用pina和vite
v-if和v-for优先级不同
- vue2中v-if低于v-for的优先级
- vue3中v-if高于v-for的优先级
watch,computed的不同
复用逻辑提取方式不同
- vue3不推荐使用mixin进行复用逻辑提取,而是推荐写成hook、因为mixin写法更贴合选项时api、和组合式api就没办法配合
- mixin 重复出现的数据和操作逻辑会提取出来到一个文件里.mixin
- hook使用方式,创建一个文件,哪里需要使用时引入该文件即可
v-model
-
v-model应用于组件时,监听的事件和传递的值改变
-
v-model主要是用在表单
<input>、<textarea>、<select>元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素 -
v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源
-
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件
- checkbox 和 radio 元素使用 checked property 和 change 事件
- select 元素使用 value property 和 change 事件
- 注意:v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件
-
相同点
用在表单上都是给input绑定一个 value 值和 input 事件(默认情况)
- 不同点(主要用在自定义组件上)
vue2
- v-model 只能使用一次
- 默认传递 vulue属性,接受 input 事件
- 默认传递的属性和事件可通过 model 选项进行修改
vue3
- v-model 只能使用一次,但可以在v-model后添加参数可使用多次(因为去除了.sync修饰符,但把相应功能合并到了 v-model 上,所以可以使用多次)
- 默认传递 modelValue 属性,接受 update:modelValue 事件,当在v-model后添加参数时如v-model:myPropName,则传递为 myPropName 属性,接受 update:myPropName事件
- 默认传递的属性和事件无法修改,必须是modelValue 和 监听update:modelValue事件
<input type="text" v-model="searchText">
等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
<input type="radio" id="man" value="Man" v-model="sex" />
等同于
<input type="radio" id="man" value="Man" :checked="sex === 'Man'" @change="handleSexChange" />
vue3中父组件通过v-model:属性名=‘属性值’的形式给子组件传递数据
<Son v-model:num = 'num'></Son>
子组件通过defineProps定义接受的数据
const props = defineProps({
num:{
type:Number,
default:0
}
})
子组件要修改父组件传来的数据:通过$emit事件去出发父组件修改 修改时定义的一个事件名是写死的,必须是update:属性名
- 用在vue2上时
父组件:
<!-- Test.vue -->
<CustomInput v-model="searchText" />
等同于
<CustomInput
:value="searchText"
@input="newValue => searchText = newValue"
/>
子组件:
<!-- CustomInput.vue -->
<template>
<input type="text" :value="value" @input="handleInput($event)" />
</template>
<script>
export default {
props: {
value: String//必须定义value属性,否则input元素无法使用
},
methods: {
handleInput (event) {
this.$emit('input', event.target.value)//需要向外派发 input 事件去更新 value 值
}
}
}
</script>
如果想在 CustomInput.vue 中使用v-model,则需要定义同时具有 getter 和 setter 的计算属性
get 方法需返回 value prop,而 set 方法需触发相应的事件
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>
<!-- CustomInput.vue -->
<template>
<input type="text" v-model="customValue" />
</template>
<script>
export default {
props: {
value: String
},
computed: {
customValue: {
get () {
return this.value
},
set (value) {
this.$emit('input', value)
}
}
}
}
</script>
如果在 CustomInput.vue 中我们不想使用默认 props 里的 value 名和 触发 input事件,那我们可以使用 model 选项,它可以通过 prop 自定义 value 字段名并通过 event 自定义触发的 事件名
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>
<!-- CustomInput.vue -->
<template>
<div>
<input type="text" :value="customValue" @input="handleInput($event)" />
</div>
</template>
<script>
export default {
model: {
prop: 'customValue',
event: 'customInput'
},
props: {
customValue: String
},
methods: {
handleInput (event) {
this.$emit('customInput', event.target.value)
}
}
}
</script>
- v-model用在vue3组件 默认情况下 vue2 的 v-model 用在表单上 和 用在组件上 表现形式是一样的。都是 :value 和 @input,但在vue3上 v-model的表现就不一样了 用在表单上是:
<input v-model="searchText" />
等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
**而当使用在一个组件上时,v-model 会被展开为如下的形式:**
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
ts更好地配合
toRef 和 toRefs
toRef 和 toRefs 是 Vue3 中的响应式转换工具函数,它们的存在主要有以下两点原因:
- 在 Vue 中,直接使用响应式对象的属性可以实现属性的双向绑定和响应式更新。但是有时候需要将某个属性提取出来作为独立的 ref 对象,这样可以在不影响源对象的情况下,对属性进行单独的访问和修改。toRef 函数正是为了解耦属性的关联,将属性转换为一个独立的 ref 对象。
- Vue 的组件系统中,父组件向子组件传递属性时,需要将这些属性声明为响应式对象。但是,如果直接将整个响应式对象传递给子组件,子组件无法通过解构或者直接访问每个属性。这时,toRefs 函数就可以将整个响应式对象转换为一个普通对象,每个属性都是独立的ref对象,子组件可以轻松解构和访问这些属性
toRef 和 toRefs 就是用来创建响应式的引用的,主要用来取出响应式对象里的属性,或者解构响应式对象,解构出来的属性值依然是响应式属性,如果不用 toRef 或者 toRefs,直接解构会丢失响应式效果
<script setup>
import { reactive } from 'vue';
let info = reactive({
name: 'Echo',
age: 26,
gender: 'Male',
})
// 这样解构会丢失响应式效果
let { name, age, gender } = info;
let age = toRef(info, 'age');//单个属性
let { name, age, gender } = toRefs(info);//多个属性
</script>
-
toRef
- toRef 函数可以将一个响应式对象的属性转换为一个独立的 ref 对象。
- 返回的是一个指向源对象属性的 ref 引用,任何对该引用的修改都会同步到源对象属性上。
- 使用 toRef 时需要传入源对象和属性名作为参数
-
toRefs
- toRefs 函数可以将一个响应式对象转换为一个普通的对象,该对象的每个属性都是独立的 ref 对象。
- 返回的对象可以进行解构,每个属性都可以像普通的 ref 对象一样访问和修改,而且会保持响应式的关联。
- toRefs 的使用场景主要是在将响应式对象作为属性传递给子组件时,确保子组件可以正确地访问和更新这些属性。
-
相同点
- toRef 和 toRefs 都用于将响应式对象的属性转换为 ref 对象。
- 转换后的属性仍然保持响应式,对属性的修改会反映到源对象上。
- 不管是使用 toRef 还是 toRefs 将响应式对象转成普通对象,在 script 中修改和访问其值都需要通过 .value 进行。
-
不同点
- toRef 修改的是对象的某个属性,生成一个单独的 ref 对象。
- toRefs 修改的是整个对象,生成多个独立的 ref 对象集合。
- toRefs 适用于在组件传递属性或解构时使用,更加方便灵活,而 toRef 更适合提取单个属性进行操作
v-router
模式
- hash:使用window.location.hash属性以及onhashchange事件监听浏览器地址hash值的变化,仅hash符号前的内容被包含在请求中,改变hash不会重新加载页面,对后端来说不会有404,hash变化会增加一个访问历史记录,push(),replace()
- history:发生改变时只改变路径不刷新页面
- history对象保存所有访问的页面,history.length,pushstate新增,replacestate替换,history.go(-2/2)后退/前进、back后退,forWord前进
- 该模式下刷新页面无法从服务端匹配到静态资源,因路由分两种,前端路由和后端路由,若是该history路由只是改变了地址的显示,并未发请求,但刷新就会发送get请求,由于后端未配置相应路由就返回404
路由守卫
- 全局前置守卫:beforEach(to,form,next)=>{next()}跳转前操作、next是正常跳转,不写就不会跳转,可用在验证用户访问权限
- 全局后置守卫:afterEach()跳转后操作,可用在路由切换将页面滚动位置,返回到顶部window.scrollTo(0,0),页面跳转后判断当前页面宽度大小后是否隐藏侧边栏
- 路由独享守卫:beforeEnter(),在路由配置中定义,只有访问到此路由时才触发
- 组件内守卫
- beforRouterEnter(){}在渲染该组件对应路由被调用,不能获取组件实例this,因执行前组件实例还没被创建
- beforRoutundate(){}当前路由改变,但该组件被复用时可调用this,动态参数的路径/foo/:id
- beforRouteLeave(){}离开该组件时可调用可访问this,可处理不未保存时或正在会议中离开的情况
路由传参
push传参的区别
生命周期的变化
Vue 3引入了两个新的调试钩子onRenderTracked和onRenderTriggered。
在Vue 3中,销毁相关的生命周期钩子从beforeDestroy和destroyed变为onBeforeUnmount和onUnmounted,以保持与挂载钩子的一致性。
什么是虚拟Dom
真实dom:操作真实dom以及后续更新,如果没有虚拟dom的话就会全量更新整个dom树,但是真实dom操作是很消耗资源的,虚拟dom就是为了提高性能的
虚拟dom是相对于游览器所渲染出来的真实dom的、用js按照dom结构来实现的一个树形结构,这个树形结构可以完全表现真实dom的每个dom节点以及dom节点的属性真实dom 有了虚拟dom后,每次dom的更改就变为了js对象的属性的更改,这样一来就能查询js对象的属性变化要比查询dom树的性能开销小
那虚拟dom是怎么解决这个问题的?diff算法 通过依赖更新触发,然后生成新的虚拟dom,将新的虚拟dom传入path中,通过diff算法对比新旧两个虚拟dom,如果虚拟dom没有改变则不操作,改变了就操作真实dom,,减少了dom操作,只会操作以及改变的dom
- 为什么说虚拟dom可提高性能 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没必要的dom操作来提高性能
当数据发生变化时,vue是怎么更新节点的? 先根据真实dom生成一个虚拟dom,当虚拟dom某个节点的数据改变后会生成一个新的节点,然后新旧节点做对比,发现有不一样的地方就直接修改在真实的dom上,然后使旧节点的值为节点
diff的过程就是就是调用名为patch函数,比较新旧节点,一边比较一边给真实的dom打补丁
1、vue项目的路由权限是怎么开发的?
2、如何做大文件上传的功能?
3、单点登录是怎么做的?
4、refresh_token的逻辑是怎么写的?
5、如何使用自定义指令控制按钮级别的权限?
6、websocket有没有接触过?
7、说一下websocket的心跳检测和断线重连。
8、有没有封装过npm组件库?
9、有没有做过项目代码优化和打包优化?
10、大屏可视化如何实现屏幕自适应?
强缓存和协商缓存分别什么时候用
BFC简单介绍一下 promise和async await的关系
axios
案例:www.jianshu.com/p/c9e05ac3a…
axios内部运作流程

原生ajax
- 优点:局部更新;原生支持
- 缺点:可能破坏浏览器后退功能;嵌套回调 原生ajax及4个步骤和过程:
var xhr = new XMLHttpRequest();//1、创建异步对象
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");//2、设置请求基本信息,并加上请求头
xhr.open('post', 'test.php' ,'true');//3、配置用get还说post请求,发送的url请求是否是异步
xhr.send('name=Lan&age=18');//4、发送请求
xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {// 这步为判断服务器是否正确响应
console.log(xhr.responseText);} };//获取异步调用返回的数据
手写ajax:
function ajx(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest() //初始化一个实例
xhr.open('GET', url, true) //true是开启异步请求,false是同步请求
chr.onreadystatechange = function () {
if (xhr.readystate === 4) {
//0未初始化,还未调用send()、1载入,已调send(),正在发送请求,2载入完成,send()执行完成,已接受全部响应内容,3交互:正在解析响应内容还未发送回来,4完成:响应内容解析完成,可在客户端调用
if (xhr.status === 200) {
//状态码,2XX:成功处理请求,3xx需要重定向,浏览器直接跳转,4xx客户端请求错误,5xx服务端错误
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status === 404) { reject(new Error('404 not found')) } }}
xhr.send(null)
})
return p }
const url = '/data/test.json'
ajx(url).then(res => console.log(res)).catch(err => console.log(err))
jqueryAjax
在原生的ajax的基础上进行了封装;支持jsonp
var loginBtn = document.getElementsByTagName("button")[0];
loginBtn.onclick = function(){
ajax({
type:"post",
url:"test.php",
data:"name=lan&pwd=123456",
success:function(data){
console.log(data);
}
});
}
fetch
- 优点:解决回调地狱
- 缺点:API 偏底层,需要封装;默认不带Cookie,需要手动添加; 浏览器支持情况不是很友好,需要第三方的ployfill
fetch('http://www.mozotech.cn/bangbang/index/user/login', {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams([
["username", "Lan"],["password", "123456"]
]).toString()
})
.then(res => {
console.log(res);
return res.text();
})
.then(data => {
console.log(data);
})
axios
- 特点:
- 支持浏览器和node.js
- 支持promise
- 能拦截请求和响应
- 能转换请求和响应数据
- 能取消请求
- 自动转换JSON数据
- 浏览器端支持防止CSRF(跨站请求伪造)
axios({
method: 'post',
url: '/abc/login',
data: {
userName: 'Lan',
password: '123'
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
同时发起多个请求:
如何将axios异步请求同步化处理?
//使用 asyns/await
async getHistoryData (data) {
try {
let res = await axios.get('/api/survey/list/', {
params: data
})
this.tableData = res.data.result
this.totalData = res.data.count
} catch (err) {
console.log(err)
alert('请求出错!')
}
}
如何中断(取消)axios的请求?
axios怎么解决跨域的问题?几种跨域方式了解一下