Vue3常见API:
- reactive:
reactive返回proxy对象,如果发现被访问的属性是引用类型,还会用reactive递归处理,属性是可以被修改的。- shallowReactive:
shallowReactive返回proxy对象,和reactive的区别是只建立第一层为响应式,如果发现被访问的属性是引用类型也不会进行递归代理,属性是可以被修改的。- readonly:
readonly返回proxy对象,如果发现被访问的属性是引用类型会进行递归处理,但是属性是只读的,不能修改。可以做props传递给子组件使用。- shallowReadonly:
shallowReadonly返回proxy对象,shallowReadonly 仅对对象的第一次层属性进行只读处理,对于嵌套的对象或数组,它不会进行只读保护。
Vue3 新特性有哪些?
1、优化响应式系统:
- 使用ES6的 Proxy配合Reflect 代替vue2的 Object.defineProperty ,不仅可以更高效地追踪依赖关系,还解决了 Vue 2 中响应式无法追踪数组索引和对象属性添加的问题。
2、更好的支持 typeScript:
Vue3在源码使用了 TypeScript,可以为开发者 提供更好的类型检查和类型推断。3、新增 Composition API:
- Composition API 提供了一种新的代码组织和复用方式,它允许开发者将逻辑拆分成独立的函数(称为“组合函数”),并在setupm中按需使用。这使得代码更加模块化,易于测试和维护,特别是在大型项目中。
4、新增组件:
- Fragment: template 模板可以有多个根元素。
- Teleport 传送门,允许Teleport中的内容传送到任意的 DOM 中。
- Suspense 用于处理异步组件的加载状态,提供了更好的用户体验。
5、支持多个根节点
6、Tree-shaking:支持摇树优化:
Vue3.x中的核心API都支持tree-shaking,这些API都是通过包引入的方式而不是直接挂载在vue原型上,只会对使用到的功能或特性进行打包(按需打包),这意味着更多的功能和更小的体积。7、diff算法的优化:
- vue2中虚拟dom进行全量对比
- Vue 3 的虚拟 DOM Diff 算法通过引入静态标记(PatchFlag)和静态提升等优化手段,显著提高了渲染性能。PatchFlag 允许 Vue 在比较新旧节点时跳过未发生变化的静态部分,只关注动态变化的部分。静态提升则通过复用未发生变化的节点,减少了不必要的节点创建和销毁操作。
8、移除了一些功能:
- 移除v-on的键盘修饰符,鼓励开发者使用键盘事件(如 keyCode 或 key)来替代;
- 移除过滤器filter。
Vue3性能提升主要是通过哪几方面体现的?
1、编译阶段优化
- diff算法优化
vue3在diff算法中相比vue2增加了静态标记Patchflag,在编译template模板时,给vnode添加的一个PatchFlag标识信息,这个标识信息可以反映哪些vnode具有动态绑定的属性或子节点,在与上次的节点进行对比时候,Vue会利用patchFlag来高效地识别出哪些是需要更新的节点,并仅对这些节点进行对比和更新操作。这样可以避免不必要的DOM操作,从而提升渲染性能。- 静态提升
Vue3会对不参与更新的元素做静态提升,只会在初始化的时候创建一次,重新渲染时直接复用。在初始化的时候会对该静态元素打上特殊静态标记,值为-1,表示静态提升,(特殊标志是负整数表示永远不会用于 Diff),做了静态提升后,未参与更新的元素,被放置在render 函数外,每次渲染的时候直接复用即可。
没做静态提升之前,未参与更新的元素也在render函数内部,会重复创建阶段。详细:
- Vue 2在对比较新旧节点时,会检查每个节点是否是静态的,如果是静态的会跳过这个节点及其子节点的比较。虽然会跳过静态节点的比较,但是Vue2并没有做静态提升,静态根节点还是在render函数内,所以这些静态根节点每次都会被重新创建渲染。
- vue3在diff算法中相比vue2增加了静态标记Patchflag,在编译template模板时,给vnode添加的一个PatchFlag标识信息,这个标识信息可以反映哪些vnode具有动态绑定的属性或子节点,在与上次的节点进行对比时候,Vue会利用patchFlag来高效地识别出哪些是需要更新的节点,并仅对这些节点进行对比和更新操作。对不参与更新的元素做静态提升,只会在初始化的时候创建一次,重新渲染时直接复用。在初始化的时候会对该静态元素打上特殊静态标记,值为-1,表示静态提升,这样可以避免不必要的DOM操作,从而提升渲染性能。
2、源码体积优化
- 相比Vue2,Vue3整体体积变小了,除了移出一些不常用的API,最重要的是优化了对 Tree shanking的支持。Vue3中的核心API都支持tree-shaking,这些API都需通过包引入的方式才能使用,只会对使用到的功能或特性进行打包(按需打包),这意味着更多的功能和更小的体积。
3、响应式系统优化
- vue2主要采用 object.defineProperty来劫持对象属性,通过进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式拦截。
- vue3采用proxy重写了响应式系统,因为proxy是对整个对象进行监听的,所以无需递归遍历对象的属性。
- 可以监听到属性的添加、删除操作。
- 可以监听到数组的索引和数组length属性的操作。
4、事件优化
- 在Vue2中,每次渲染时都会重新创建事件处理函数,即使是相同的事件处理逻辑。
- 在Vue3中,引入了缓存事件处理函数的概念,它会将事件处理函数在编译阶段缓存起来,重新渲染的时候直接复用。
// 静态提升例子:
// Vue2的静态节点
render(){
return createVNode("div", null, "Hello World")
}
// Vue3的静态节点
const staticLifting = createVNode("div", null, "Hello World")
function render(){
// 直接使用 staticLifting 即可
}
<button @click="i++">plus</button>
// vue2
render(ctx){
return createVNode("button", {
onClick: function($event){
ctx.i++;
}
})
}
// vue3
render(ctx, _cache){
return createVNode("button", {
onClick: cache[0] || (cache[0] = ($event) => (ctx.i++))
})
}
Vue3和Vue2的区别?
1. 响应式数据的监听方式不同:
- Vue2 的响应式数据监听是采用ES5的Object.definePropert() 对数据进行劫持的;
- Vue3 中使用ES6的Proxy()对数据进行代理劫持。
- 相比Object.definePropert(),ES6的Proxy()有以下优点:
- Proxy代理监听的是整个对象,不需要通过递归+遍历为对象属性设置getter和setter进行劫持,提高了性能;
- Proxy可以监听到对象属性的动态添加和删除;
- 可以监听到数组的索引和数组length属性的操作;
- 如果对象的属性也是对象的话,只有在被访问的时候才会进行递归响应观测,提高了性能;
2. Vue3新增了组合式(Composition API):
- Vue2是使用options API的,options API内部的逻辑点是碎片化的,一个功能逻辑会出现在不同的位置,通过定义methods,computed,watch,data等options API,共同处理页面逻辑。
- Vue3新增了Composition API(组合API),同时仍然兼容Vue2中的Options API。组合式API,组件根据逻辑功能来组织代码的,一个功能所定义的所有 API 会放在一起,通过函数分割复用代码,将其封装成一个hooks,hooks需要在setup函数中使用。
3. 定义数据变量和方法不同:
- vue2是在对应的选项中定义变量、方法、计算属性等。在data选项中定义响应式数据,在method中定义方法。
- Vue3需要从vue中导出reactive、ref、computed等API,这些API需要在一个setup钩子函数中使用,通过使用reactive、ref来定义响应式数据。
4. template模板支持:
- Vue3 template 支持多个根节点;
- Vue2 template 只能支持单个根节点。
5. 生命周期函数:
- vue2的生命周期是选项式的,,主要生命周期函数是beforeCreate、created、beforeMount、Mounted、beforeUpdate、updated、beforeDestory、destroyed;
- vue3的生命周期是组合式的,需要通过包引入的方式才可以时使用;
- vue3用setup钩子取代了beforeCreate和created钩子函数,setup函数在beforeCreate之前调用;
- vue3将beforeDestory和destroyed这两个钩子函数的名称改为onBeforeUnmount和onUnmounted。
6. Vue实例的创建方式不同:
- Vue2是通过new一个Vue实例来创建Vue实例对象的;
- Vue3需要引入createApp方法,然后调用该方法创建Vue对象。
// Vue2
import Vue from 'vue';
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app');
// Vue3
import {createApp} from 'vue';
import App from './App.vue'
createApp(App).mount('#app')
Options Api与Composition Api的区别?
代码组织:
1、Options Api:选项式API内部的逻辑点是碎片化的,一个功能逻辑会出现在不同的位置,通过定义methods,computed,watch,data等选项API,共同处理页面逻辑。当组件变得复杂时,会降低代码的可读性和可维护性。
2、Composition API(组合式API),使用 ref、reactive、computed、watch 等函数来替代 Options API 中的对应选项。组件根据逻辑功能来组织代码,通过函数分割复用代码,一个功能所定义的所有 API 会放在一起,这使得代码更加集中和模块化,易于阅读和维护。( 更加的 高内聚,低耦合 )。逻辑复用:
- 在vue2.0中,是通过mixin来实现逻辑复用的,当使用多个mixin会存在两个非常明显的问题:命名冲突、数据来源不清晰
- Composition Api是通过函数复用,通过将相同逻辑功能的代码封装成一个函数,这些代码片段可以包含状态、计算属性、方法等,可以在多个组件中重复使用,而不会引起命名冲突或数据来源不明确的问题。
总结:- 在代码组织和逻辑复用方面,Composition API是优于Options API
- 因为Composition API几乎是函数,会有更好的类型推断;
- Composition API 对 tree-shaking 更友好,从而生成更小的包;
- Composition API中见不到this的使用,减少了this指向不明的情况;
- 如果是小型组件,可以继续使用Options API,也是十分友好的。
- 更好的测试,由于 Composition API 中的逻辑更加模块化,它们也更易于进行单元测试。
Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?
1、vue2是采用 Object.defineProperty来监听对象的操作的,通过遍历+递归的方式给每个属性添加getter和setter进行劫持。
存在以下的问题:
- 检测不到对象属性的添加和删除,Vue提供了
delete删除,但是这需要手动调用,有额外的负担;
- 监听不到通过数组索引和数组length属性的操作;
- 在初始化阶段,需要对对象属性进行遍历监听,如果是嵌套对象,即使没有被访问到,也会被递归监听,当对象过深的时候会造成性能问题。
2、Proxy监听的是整个对象的操作:
- Proxy 创建了一个对象的代理,这个代理可以拦截针对对象的任何操作(如读取、赋值、函数调用等),而不仅仅是属性访问。因此,它可以更全面地追踪对象的变化。
- Proxy不需要预先知道要监听哪些属性,它可以动态地处理对象的属性添加和删除。
- 可以监听到数组的索引和数组length属性的操作;
- 对于嵌套对象,只有在被访问到的时候才会对其进行递归响应观测,这有助于提高性能;
- Proxy提供了多达13种拦截方法,这些方法可以覆盖对象的各种操作,提供了更大的灵活性和控制力。
Proxy 只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢?
当访问到深层嵌套对象的话,会触发getter,会判断当前 Reflect.get 的返回值是否是已经代理过的对象,如果是一个普通对象则再使用 reactive 方法做代理, 这样就实现了深度观测。
说说Vue 3.0中Treeshaking特性?举例说明一下?
1、什么是Tree shaking?
Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫 (Dead code elimination)简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码
2、Tree shaking原理?
Tree shaking是基于ES6模块化方案 (import与exports),模块之间的依赖关系是高度确定和静态的,与运行状态无关,可以进行可靠的静态分析。在打包过程中 静态分析 模块之间的导入导出,确定哪些导出的模块没有被引用并打上标记,最终将其删除。
- 在Vue2中,很多API 都是挂载在Vue原型上的,程序无法检测到该对象的哪些属性在代码中被使用到。
- 而Vue3优化了对tree shaking的支持,所有的核心API都支持Tree Shaking, 需要通过包引入的方式才可以使用。如果您不使用其某些功能,它们将不会包含在您的基础包中。
Tree shaking无非就是做了两件事:
- 编译阶段利用ES6 Module判断哪些模块已经加载
- 判断哪些模块和变量未被使用或者引用,进而删除对应代码
3、Tree shaking作用(好处)?- 减少程序体积(更小);
- 减少程序执行时间(更快);
- 便于将来对程序架构进行优化(更友好)。
watch 和 watchEffect 的区别?
watch 和 watchEffect 都是监听器,watchEffect 是一个副作用函数。
它们之间的区别有:
- watch :既要指明监视的数据源,也要指明监视的回调。
- watchEffect 可以自动监听数据源作为依赖。不用指明监视哪个数据,监视的回调中用到哪个数据,那就监视哪个数据。
- watch 可以访问改变之前和之后的值,watchEffect 只能获取改变后的值。
- watch 依赖的属性改变后才会执行,初始化的时候不会立即执行,但是可以通过 watch 的配置项 immediate 来改变;而 watchEffect 初始化的时候会立即执行。
watchEffect有点像 computed :- computed 注重的计算出来的值(回调函数的返回值), 所以必须要写返回值。
- watcheffect注重的是过程(回调函数的函数体),所以不用写返回值。
ref与reactive的区别?
- ref与reactive 是 Vue3 新推出的主要 API 之一,主要用于创建响应式数据。
- ref 函数创建的响应式数据,在模板中可以直接被使用,在 JS 中需要通过 .value 的形式才能使用。
- ref 函数可以接收原始数据类型与引用数据类型。
- reactive 函数只能接收引用数据类型。
- ref 底层还是使用 reactive 来做,ref 是在 reactive 上在进行封装的,增强了其能力,使它支持了对原始数据类型的处理。
- 在 Vue3 中 reactive 能做的,ref 也能做,reactive 不能做的,ref 也能做。
- ref还能获取组件的实例。
setup() 函数特性:
- setup 函数有两个参数:(props、context(包含attrs、slots、emit));
- setup函数在 生命周期 beforeCreate 和 created 两个钩子函数之前执行;
- setup函数中不能使用this,因为 setup 函数执行时,组件实例尚未被创建,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了undefined;
- 与模板一起使用:需要返回一个对象,将需要在模板中使用的变量和方法return 出去,不然无法在模板中使用
- setup函数只能是同步的不能是异步的。
- setup 函数中的 props 是响应式的,它是一个reactive。不能使用ES6的解构来结构props,因为它会消除 props 的响应性。如果需要解构 props,可以通过使用 setup 函数中的 toRefs 来完成解构操作:
import {toRefs} from 'vue'
setup(props) {
const { name } = toRefs(props);
console.log(name.value);
onMounted(() => {
console.log('name: ' + props.name);
})
}
script setup 是干啥的?
scrtpt setup 是 vue3.2 的语法糖,简化了组合式 API 的写法,并且运行性能更好。
script setup 语法糖的特点:
- 属性和方法无需返回,可以直接在template模板中使用。
- 引入组件的时候,会自动注册,无需通过 components 手动注册。
- 使用 defineProps 接收父组件传递的值、defineEmits 定义自定义事件。
- 使用 useAttrs 获取属性,useSlots 获取插槽。
- 默认不会对外暴露任何属性方法,如果有需要可使用 defineExpose 指定暴露的属性、方法。
- defineOptions可以关闭属性透传和定义组件name值。
export const enum PatchFlags {
// 表示vnode具有动态textContent的元素
TEXT = 1,
// 表示vnode具有动态的class
CLASS = 1 << 1,
// 表示具有动态的style
STYLE = 1 << 2,
// 表示具有动态的非class和style的props
PROPS = 1 << 3,
// 表示props具有动态的key,与CLASS、STYLE、PROPS冲突
FULL_PROPS = 1 << 4,
// 表示有监听事件(在同构期间需要添加)
HYDRATE_EVENTS = 1 << 5,
// 表示vnode是个children顺序不会改变的fragment
STABLE_FRAGMENT = 1 << 6,
// 表示children带有key的fragment
KEYED_FRAGMENT = 1 << 7,
// 表示children没有key的fragment
UNKEYED_FRAGMENT = 1 << 8,
// 表示vnode只需要非props的patch。例如只有标签中只有ref或指令
NEED_PATCH = 1 << 9,
// 表示vnode存在动态的插槽。例如动态的插槽名
DYNAMIC_SLOTS = 1 << 10,
// 表示用户在模板的根级别存在注释而创建的片段,这是一个仅用于开发的标志,因为注释在生产中被剥离
DEV_ROOT_FRAGMENT = 1 << 11,
// 以下都是一些特殊的flag,它们不能使用位运算进行匹配
// 表示vnode经过静态提升
HOISTED = -1,
// diff算法应该退出优化模式
BAIL = -2
}
vue3 reactive响应式原理:
- vue3 响应式主要是采用发布者订阅者模式 + es6的 Proxy 代理实现的。
- 首先判断目标对象是否是一个对象,如果不是,直接警告提示。
- 判断目标对象有没有被 Proxy 代理过,如果已经被代理过了直接返回目标对象。
- 否则调用createReactiveObject方法对目标对象进行 Proxy 代理,设置getter和setter进行拦截。当 Proxy 对象中的属性被访问的时候,会触发getter,此时会去获取值,如果该值是对象且没有被Proxy代理过的话,会递归调用reactive去实现对其进行 Proxy 代理。在getter中会调用track收集effect;当 对象的属性被修改或者新增了属性的是,会调用trigger去通过订阅该属性的effect做出相应的更新;
- track方法的作用就是收集effect,收集的effect会保存在全局WeakMap结构的targetMap对象中,其key值是需要代理的目标对象,value值是也是一个depsMap对象,它是一个 Map 结构的对象,是用来保存代理对象的key值所对应的所有effect,所有effect都是存储在Set结构的集合中的。
- trigger方法主要是通知属性对应的所有effect去更新,会根据代理对象target去targetMap中查找代理对象所对应的depsMap,然后根据访问的代理对象的key值去depsMap查找该key的所对应的effect集合,然后遍历去执行每个effect里的更新方法,进而做出相应的更新操作。
- effect是一个包装函数,接收一个副作用函数,它通常代表了一个需要响应数据变化的操作。当属性被访问的时候,会触发getter,此时会将当前激活的activeEffect收集到当前属性的effect收集器中,待收到数据发生变化的消息时,会调用自身对应配置下的更新方法重新继进行更新。
Vue3 ref原理:
ref利用了ES6的类的属性访问器原理,它有一个value属性,用于保存ref的值,在构造函数被创建的时候会在constructor获取初始值,如果值是对象的话,会使用reactive进行响应代理。value被设置了get和set进行监听,当value被访问的时候会触发get,在get里调用track方法进行依赖的收集。当对value进行赋值的时候会触发set,在set里会调用trigger方法通知所有的依赖该属性的方法也就是所有的effect进行更新。如果修改的值是一个对象的话,会调用reactive来对其进行响应式处理。
为什么 ref 类型数据,必须要通过 .value 访问值呢?
a. 因为 ref 既可以处理基本数据类型的数据,也可以处理对象类型的数据,但是基本数据类型的数据无法通过 proxy 进行代理。
b. 而 vue 结合ES6的类的属性访问器原理,通过 get value() 和 set value() 定义了两个属性函数,通过主动触发这两个函数的形式来进行依赖收集和依赖触发。
c. 所以我们必须通过 .value 来保证数据响应式。
computed的原理:
- 计算属性有函数式写法和对象写法,当用户传入的是一个函数的时候,默认使用的是getter只读操作;当用户传入的是一个对象时,用户可以设置getter,setter,此时计算属性具备可读可写的能力。
- 在vue3中实现计算属性中,第一步就是处理、收集用户设置的getter和setter;
- 然后实例化一个计算属性的类,在类的构造器中,会实例化一个effect,这个effect接收用户的设置的getter和一个调度函数,用于计算计算属性的值和更新计算属性的_dirty属性。计算属性的effect使用的是lazy配置,在初始化的时候是不会立即执行去获取值的,因为要考虑到计算属性是否有被使用。
- 计算属性类中有一个_dirty属性,默认为true,用于判断计算属性是否需要重新计算;还有一个_value属性,利用ES6类的属性访问器原理,被设置了get和set进行监听,当计算属性被修改时,会触发set,在set中只是简单的执行用户设置的setter;当计算属性value被访问的时候会触发get,在get里会调用effect的run方法也就是用户设置的getter去获取值,此时会访问依赖属性的值,依赖的属性会将当前活跃的计算属性的effect收集起来。在get中还会将_dirty设置为false,如果依赖属性没有发生变化是不需要重新计算值的。
- 当依赖的属性发生变化的时候,会去遍历该属性所有的effect,此时会执行计算属性effect的调度函数,将_dirty设置为true,当计算属性再次被访问的时候,此时的_dirty为true,会重新计算值。