vue是什么
是一个动态构建用户界面的渐进式JavaScript框架。用来创建单页应用的 web 应用框架。
优势:Vue 是一个轻量级框架,只关注图层,是一个构建数据的视图集合,大小只有十几KB。vue简单易学,而且通过 MVVM 思想实现了数据的双向绑定,让开发者不用再操作 dom 对象,有更多时间去思考业务逻辑。而且 vue是组件化的,通过组件,将单页应用中的各个模块拆分成单独的组件,提高了复用性。在更新视图的时候,还提供了虚拟节点,将新旧虚拟节点进行对比,然后更新视图。
Vue与React
相同点:
都有组件化思想
都是数据驱动视图
都支持服务端渲染
都有虚拟DOM
不同点:
数据流向不同。前者是双向数据流,后者是单向数据流。
数据变化的实现原理不同。前者使用的是可变的数据,后者使用的是不可变的数据。
diff算法不同。前者使用双指针,边对比,边更新 DOM。后者主要使用 diff 队列保存需要更新的一些 DOM,然后得到patch 树,在统一进行批量更新 DOM。
MVC与MVVM
MVC:
M(模型层):处理应用程序数据逻辑的部分(存数据、取数据)
V(视图层):处理数据显示的部分(页面展示、Dom操作)
C(控制层):处理用户交互的部分(控制模型层与视图层的关联)
MVVM:
M(模型层):处理数据与业务逻辑的部分
V(视图层):负责数据展示的部分
VM(视图模型层):负责从模型层监听数据的变化从而更新视图层,用来处理用户交互操作的部分
两者最大区别:MVVM 实现了模型层与视图层的自动同步,当数据发生变化时,不用手动操作DOM元素来改变视图层的显示,而是改变了数据对应的视图层自动更新
双向数据绑定(可以看我之前写的vue2双向数据绑定的源码解析Vue2手写源码---响应式数据的变化_想学好前端的小宝的博客-CSDN博客)
vue 采用的是数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() (vue3 通过 Proxy进行劫持)来劫持各个属性的 getter 与 setter 方法,然后再数据变动的时候,发送消息给订阅者,触发相应的监听回调。主要步骤:
先使用 数据监听器Observe 对数据对象上的所有属性都添加上 getter 和 setter 方法。这样如果数据有发生变动的话,就能拿到最新值。
再使用 compile 进行模板的解析。将模板中的变量替换成数据,然后初始化渲染视图。并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
创建一个 订阅者 watcher ,在自身实例化时,往属性订阅器(dep)中添加自己,然后一旦属性变动,就会调用 dep.notice() 方法通知 对应的 watcher 调用 update() 方法进行更新,从而更新视图。
MVVM 作为数据绑定的入口,整合了 Observe、Compile、Watcher三者。通过 Observe 监听 模型层的数据变化,通过 Compile 来编译解析 模板指令,最后通过 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,可以收到属性的变化通知并执行相应的函数,从而达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model 变更的双向绑定效果。
使⽤ Object.defineProperty() 来进⾏数据劫持有什么缺点
在对⼀些属性进⾏操作时,使⽤这种⽅法⽆法拦截,⽐如通过下标⽅式修改数组数据或者给对象新增属 性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来 说,对于数组⽽⾔,⼤部分操作都是拦截不到的,只是 Vue 内部通过重写函数的⽅式解决了这个问题。
Vue3.0 通过使⽤ Proxy 对对象进⾏代理,从⽽实现数据劫持。它可以完美的监听到任何方式的数据改变,唯⼀的缺点是兼容性的问题,因为 Proxy是 ES6 的语法。
computed 与 watch区别
前者支持缓存,只有依赖的数据发生变化时,才会重新计算。后者不支持缓存,只要数据发生变化,就会触发相应操作。
前者不支持异步,有异步就无法监听数据变化。后者支持异步
前者一个属性由另外的属性计算而来的话,这个属性也依赖与另外的属性。
前者的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
后者监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会出大其他操作,函数有两个的参数:
immediate:组件加载立即触发回调函数
deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch
插槽(slot)
是子组件的一个模板标签元素。
默认插槽:在 slot 没有指定 name 属性值时候,一个默认的显示插槽。
具名插槽:带有 name 属性的 slot, 一个组件可以有多个具名插槽。
作用域插槽:默认插槽、具名插槽的⼀个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不 同点是在⼦组件渲染作⽤域插槽时,可以将⼦组件内部的数据传递给⽗组件,让⽗组件根据⼦组件 的传递过来的数据决定如何渲染该插
常见的事件修饰符
.stop: 防止冒泡
.prevent: 阻止默认行为(链接跳转)
.once: 只会触发一次
.capture:进行事件捕捉(由外到内)
.self:只会触发自己范围内的事件,不包含子元素。
v-if和v-show的区别
控制手段不同。前者动态的向DOM树内增加或者删除DOM元素来控制元素的显示与隐藏。后者通过 css 中的 display属性来控制元素的显示与隐藏。
前者支持标签。后者不支持。
编译条件不同。前者只有当第一次初始值为真的话,才会开始编译渲染。后者无论初始值,都会进行编译。
运行场景不同。前者适用于条件很少改变的情况。后者使用与频繁切换的情况。
开销不同。前者有更高的切换开销。后者有更高的初始渲染开销。
Vue2给对象添加新属性,界面不刷新
Vue2是通过 Object.defineProperty 来实现数据响应式的。(简单源码讲解:Vue2手写源码---响应式数据的变化_想学好前端的小宝的博客-CSDN博客)在我们访问旧属性的时候,都会触发 getter 和 setter 方法,进而进行页面的刷新。但在我们添加新属性的时候,没有通过 Object.defineProperty 设置成响应式数据,所以也就无法触发事件属性的拦截,也就无法进行页面的刷新了。
Vue3是用 proxy 进行数据响应式的,直接动态添加新属性还是可以实现数据响应式。
解决方案:
Vue.set(target, propertyName/index, value ) 通过Vue.set() 向响应式对象中添加一个 property ,并确保这个新的 property 是响应式的,而且还会触发视图的更新。
Object.assign() 直接使用这个方法添加到对象的新属性还是不会触发更新。 需要创建一个新对象,然后合并原对象和混入对象的属性
v-model实现原理
v-model实际上是一个语法糖,它的实现主要包括属性绑定和事件监听两部分
当作用于表单元素上
动态绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动态把 message设置为当前DOM的value值
作用在组件上
在⾃定义组件中,v-model 默认会利⽤名为 value 的 prop和名为 input 的事件
本质是一个父子组件通信的语法糖,通过prop和$.emit实现。因此父组件 v-model 语法糖本质上可以修改为:
data为什么是⼀个函数⽽不是对象
在根实例对象中,data可以是一个函数也可以是一个对象,因为根实例是单例的,不会造成数据污染
在组件实例对象中,data必须是一个函数,防止多个组件实例对象之间共用一个data,会产生数据污染。如果data是函数的话,initData 时会将其作为工厂函数都会返回全新的 data 对象。
nextTick
Vue 在更新 DOM 的时候是异步更新的。当数据发生变化的时候,nextTick 会开启一个异步更新队列,视图需要等待队列中所有数据变化完成后,在统一进行更新。(简单源码解析:Vue2手写源码---响应式数据的变化_想学好前端的小宝的博客-CSDN博客)
Mixin
Mixin是面向对象程序设计语言中的类,通常作为功能模块使用。其他类可以直接访问Mixin类的方法而不用称为其子类。本质上就是一个 JS 对象,包含我们组件中任意功能选项,如 data、methods等。
我们只要将共用的功能以对象的方式传入 mixins
选项中,当组件使用 mixins
对象时所有mixins
对象的选项都将被混入该组件本身的选项中来。
注意事项
当组件存在与 Mixin 对象相同的选项时候,合并时组件选项会覆盖 Mixin 的选项
如果生命周期钩子有相同选项时,会合并成一个数组,然后先执行 mixin 的钩子,在执行组件的钩子
优点:增加代码的复用性
什么是虚拟DOM
它是对真实DOM的 抽象,就是使用 js 对象作为基础的树,用对象的属性来描述节点,然后通过一系列的属性将这棵树渲染到页面上。它通过事务处理机制,将多次DOM 修改的结果一次性更新到页面上,从而可以减少页面渲染的次数,减少页面重排、重绘的次数,可以提高渲染性能。
为啥使用虚拟DOM
虚拟DOM就是为了解决浏览器性能问题而被设计出来的。若一次操作DOM中有十次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这十次更新的diff内容保存到本地一个js中,最终将这个js对象一次性attach 到DOM树上,在进行后续操作。避免大量无谓的计算量。且 虚拟DOM 本质上是JavaScript的对象,它可以很⽅便的跨平台操作,⽐如服务端渲染、uniapp等。
Vue 中 key 的原理
简而言之 ,key 就是每个虚拟DOM节点的 唯一ID,也是 diff 的一种优化策略,可以根据 key ,更准确、更快的找到对应的 虚拟 DOM 节点。
在虚拟节点中,key就是虚拟 DOM 对象的标识,当数据变化时,Vue 就会根据新数据生成一个新的虚拟DOM
,随后 Vue 会根据这个 key 进行 新旧虚拟DOM 的差异对比。
对比规则:
旧虚拟DOM 找到和新虚拟DOM相同的 key
若内容没有发生改变,直接使用之前的真实DOM
若内容发生变化,则生成一个新的真实DOM,然后替换掉页面中的真实DOM
旧虚拟DOM 没找到和新虚拟DOM相同的 key
直接创建新的真实DOM,渲染到页面上
为啥不建议用index作为key
如果仅用于展示的话,使用index作为 key 是没有问题的。但要是存在逆序添加、逆序删除等破坏顺序的操作,就会产生错误的更新。所以建议还是选择每条数据的唯一标识作为 key。
diff算法
diff 算法是一种通过同层的树节点进行比较的高效算法。
特点:
比较只会在同层级进行,不会跨层级比较。
比较的过程中,循环从两边向中间进行比较。
当数据发生改变时,set方法会调用 Dep.notify 通知所有 订阅者 watcher,订阅者就会调用 patch 给真实的 DOM 打补丁,更新响应视图。
patch 函数前两个参数位为 oldVnode 和 Vnode ,主要做了四个判断:
没有新节点: 直接触发旧节点的 destory 钩子
没有旧节点: 说明是页面刚初始化,不需要比较,直接调用 createElm
通过 sameVnode 判断新旧节点是否一样
新旧节点不一样 :直接创建新节点,删除旧节点,不在进行深度比较
新旧节点一样 :直接调用 patchVnode 去处理这两个节点
找到对应的真实dom,称为el
如果都有文本节点且不相等,将el文本节点设置为Vnode的文本节点
如果oldVnode有子节点而VNode没有,则删除el子节点
如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el
如果两者都有子节点,则执行updateChildren函数比较子节点
常见的Vue性能优化
路由懒加载
keep-alive 缓存页面
使用 v-show 复用 DOM
v-for 遍历避免同时使用 v-if
长列表性能优化:单纯的展示,不做改变,就不需要做响应化。
图片懒加载
第三方组件按需引入
SSR
keep-alive
在动态组件切换的过程中,组件的实例都是重新创建的。keep-alive 包裹动态组件时,会缓存不活动的组件实例,就是缓存组件内部状态,避免重新渲染。
三个属性:
include:字符串或正则表达式,只有名称匹配的组件才会被缓存
exclude:字符串或正则表达式,任何名称匹配的组件都不会被缓存
max:数字,最多可以缓存多少组件实例
优点:
较少的CPU和内存的使⽤(由于同时打开的连接的减少了);
降低拥塞控制 (TCP连接减少了);
减少了后续请求的延迟(⽆需再进⾏握⼿);
报告错误⽆需关闭TCP连接
缺点
长时间的 TCP 连接,会导致系统资源无效占用,浪费系统资源。
说⼀下Vue的⽣命周期
Vue实例从开始创建、初始化数据、编译模板、挂载DOM --> 渲染、更新 -->渲染、卸载 称为 Vue的一个完整的生命周期。
beforeCreate(创建前):
数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据
created(创建后) :
实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染的节点还未挂载到 DOM,所以不能访问到 $el 属性。常用于异步数据获取
beforeMount(挂载前):
在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置∶ 编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
mounted(挂载后):
el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。
beforeUpdate(更新前):
响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染
updated(更新后) :
在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前):
实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。可用于一些定时器或订阅的取消。
destroyed(销毁后):
实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
created和mounted的区别
created:在模板渲染成html前调⽤,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调⽤,通常是初始化⻚⾯完成后,再对html的dom节点进⾏⼀些需要的操作。
在 created 请求异步数据的优点
能更快获取到服务端数据,减少⻚⾯加载时间,⽤户体验更好;
SSR不⽀持 beforeMount 、mounted 钩⼦函数,放在 created 中有助于⼀致性;
父子组件通信
props: 父组件向子组件传递数据
子组件设置props
属性,定义接收父组件传递过来的参数
父组件在使用子组件标签中通过字面量来传递值
//Father.vue //Children.vue props:{ // 字符串形式 name:String // 接收的类型参数 // 对象形式 age:{ type:Number, // 接收的类型为数值 defaule:18, // 默认值为18 require:true // age属性必须传递 } }
$emit: 子组件向父组件传递数据
子组件通过$emit触发
自定义事件,$emit
第二个参数为传递的数值
父组件绑定监听器获取到子组件传递过来的参数
//Chilfen.vue this.$emit('add', good) //Father.vue
ref:
父组件在使用子组件的时候设置ref
父组件通过设置子组件ref
来获取数据
//Father.vue this.$refs.foo // 获取子组件实例,通过子组件实例我们就能拿到对应的数据
兄弟组件传参
EventBus
创建一个中央事件总线EventBus
兄弟组件通过$emit
触发自定义事件,$emit
第二个参数为传递的数值
另一个兄弟组件通过$on
监听自定义事件
Bus.js
// 创建一个中央时间总线类 class Bus { constructor() { this.callbacks = {}; // 存放事件的名字 } $on(name, fn) { this.callbacks[name] = this.callbacks[name] || []; this.callbacks[name].push(fn); } $emit(name, args) { if (this.callbacks[name]) { this.callbacks[name].forEach((cb) => cb(args)); } } } // main.js Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上 // 另一种方式 Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
//Children1.vue this.$bus.$emit('foo') //Children2.vue this.$bus.$on('foo', this.handle)
$parent 或$ root
通过共同祖辈$parent
或者$root
搭建通信桥连
//Children1.vue this.$parent.on('add',this.add) //Children2.vue this.$parent.emit('add')
祖孙与后代组件之间的通信
$attrs 与$ listeners
设置批量向下传属性$attrs
和 $listeners
包含了父级作用域中不作为 prop
被识别 (且获取) 的特性绑定 ( class 和 style 除外)。
可以通过 v-bind="$attrs"
传⼊内部组件
// child:并未在props中声明foo{{$attrs.foo}}
// parent
// 给Grandson隔代传值,communication/index.vue// Child2做展开 // Grandson使⽤ {{msg}}
provide 与 inject
在祖先组件定义provide
属性,返回传递的值
在后代组件通过inject
接收组件传递过来的值
祖先组件
provide(){ return { foo:'foo' } }
后代组件
inject:['foo'] // 获取到祖先组件传递过来的值
非关系组件间之间的通信
vuex : 存储共享变量的容器
state
用来存放共享变量的地方
getter
,可以增加一个getter
派生状态,(相当于store
中的计算属性),用来获得共享变量的值
mutations
用来存放修改state
的方法。
actions
也是用来存放修改state的方法,不过action
是在mutations
的基础上进行,不能直接进行修改。常用来做一些异步操作
路由的 hash 和 history 模式
hash模式: 开发中默认的模式, url中带着 #
原理: hash模式的主要原理就是onhashchange()事件
window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1); }
使⽤onhashchange()事件的,在⻚⾯的hash值发⽣变化时,⽆需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现⻚⾯的前进和后退。虽然是没有请求后端服务器,但是⻚⾯的hash值和对应的URL关联起来了。
history模式:history 模式的 URL中没有 #,他使用的是传统的路由分发模式,在用户输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
特点: history 模式的 URL中没有 #,会好看一点。history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
API: history api可以分为两大部分,切换历史状态和修改历史状态:
修改历史状态:包括了 HTML5 History Interface 中新增的 pushState() 和replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。
切换历史状态: 包括forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作
对比:
调用history.pushState()相比于直接修改hash,存在以下优势:
pushState()设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,因此只能设置与当前URL同文档的URL
pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中
pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串
虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。 如果想要切换到history模式,前后端都要进行配置(后端配置比较复杂)。
$route 和$router 的区别
$route 是用来获取路由信息的。$ route是一个跳转的路由对象(路由信息对象)。
//常用的属性 $route.path 字符串,相当于当前页面的绝对路径。 $route.params 对象,包含路由中的动态片段和全匹配片段的键值对,不会拼接到路由的url后面 $route.query 对象,包含路由中查询参数的键值对。会拼接到路由url后面 $route.router 路由规则所属的路由器 $route.name 当前路由的名字,如果没有使用具体路径,则名字为空
$router 是用来操作路由的。 $router是VueRouter的一个实例,他包含了所有的路由,包括路由的跳转方法,钩子函数等,也包含一些子对象(例如history)
//常用的方法 this.$router.push() this.$router.replace() this.$router.go() this.$router.forward() this.$router.back()
路由跳转和链接跳转区别
使用链接跳转比较简单,但是刷新了页面。
使用路由跳转,不会刷新页面
params和query区别
query使用 path 引入,params使用 name 引入
query会在浏览器地址栏中显示参数,params则不显示
query 刷新不会丢失里面的数据,params 刷新会丢失里面的数据
vue导航守卫
全局守卫:router.beforeEach
任何路由跳转到另外一个路由的时候都会触发,而且是跳转前触发的。
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
to :要去的路由对象
from:要离开的路由对象
next:存放方法,判断是否能够跳转成功。
next() 能跳转成功
next(false) :跳转失败,中断跳转。
next({path:“/login”}) :跳转失败。跳转到指定的路径。
Vuex原理
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态。每个vuex应用的核心就是一个仓库(store)
state:相当于data; 可以通过$store.state.;
mutations: 状态改变操作⽅法。是Vuex修改state的唯⼀推荐⽅法,其他修改⽅式在严格模式下 将会报错。该⽅法只能进⾏同步操作,且⽅法名只能全局唯⼀。
actions: 异步,通过这个在组键里面调用store.dispatch('increment'),不能直接修改state中的值,只能通过 mutations 进行 修改
getters:Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
modules:管理 vuex 模块
总结:Vuex 实现了一个单向数据流,在state 中存放数据,然后必须通过 mutations 进行数据的修改,但是只能进行同步的修改,同时呢会提供订阅者模式供外部插件调用获取 state 中的数据更新。然后异步操作需要 通过 actions 进行操作,但是 actions 不能直接修改 state ,还需要通过 mutations 来修改数据。最后就是通过 state 的变化, 渲染到视图上。
mutation 和 action 的区别
mutation 都是用来修改 state 的,且是修改 state 的唯一途径。 action 是写一些业务代码、异步请求。
mutation 必须是同步执行的。 action :可以异步,但是不能直接操作 state
在视图更新的时候, 会先触发 actions, 然后 actions 再触发 mutation
Vuex属性
state :基本数据(数据源存放地)
getters :从基本数据派⽣出来的数据
mutations:提交更改数据的⽅法,同步
actions :像⼀个装饰器,包裹mutations,使之可以异步。
modules :模块化Vuex
Vuex 和 localStorage 区别
vuex 存储在内存中,localStorage以文件的方式存储在本地,而且只能存储字符串类型的数据,存储对象需要 JSON 的 stringify 和 parse 方法进行处理。读取内存比读取硬盘速度要快。
vuex 是专为 Vue.js 应用程序开发的状态管理模式。主要适用于 组件之间的传值。
localStroage 是本地存储,是将数据存储到浏览器的方法,一般在跨页面传递数据的时候使用。
vuex 能让数据响应式变化,localStroage 不能。
刷新页面时,vuex存储的值会丢失,localStroage不会。
为什么mutation 不能做异步操作
vuex 所有状态更新的唯一途径都是 mutation ,在 mutation 执行完成后,都会得到一个新的状态变更,这样 devtools 就可以打个快照存下来,然后就可以实现 time-travel 了。 如果 mutation 支持异步操作,就没有办法直到状态是什么时候更新的了,就无法进行状态的跟踪,调试就会变得困难。
Vue3.0性能提升主要是通过哪几个方面体现的
编译阶段:
diff算法优化:在会发生变化的地方增添一个 静态标记 ,下次发生变化的时候就能直接找该地方进行比较。
静态提升:对不参与更新的元素,做 静态提升 ,只会被创建一次,在渲染的时候直接复用。
事件监听缓存:默认情况下绑定事件的行为是一个动态绑定,每次都会去追踪它的变化。开启缓存后,下次进行diff算法的时候就可以直接使用
SSR优化:当静态内容大到一定量级的时候,就会使用 createStaticVnode 生成一个 static node,这些静态内容会直接被 innerHTML ,就不需要创建对象,然后根据对象渲染了。
源码体积:vue3源码整体体积,相比 vue2 来说,变小了。还 Tree shanking 了任何函数,仅在用到的时候才打包,没用到的模块都被摇掉了,打包的整体体积变小了。
响应式系统:vue2 使用 defineProperty 来劫持整个对象,然后进行深度遍历所有属性,给每个属性都添加上 getter 和 setter ,实现响应式。 vue3 采用了 proxy 重写了响应式系统。因为 proxy 可以对整个对象进行监听,所以不用深度遍历。
Object.defineProperty 与 Proxy 区别
Object.defineProperty 必须要遍历对象每一个属性进行劫持。 proxy 可以劫持整个对象,然后返回一个新的对象,可以直接操作新的对象达到响应式的目的。
proxy 可以监听数组的变化。 Object.defineProperty 不行。
Object.defineProperty 必须深层遍历嵌套的对象, proxy 不用。
Vue3 的 Composition API 和 Vue2 的 Options API 有什么不同
Options API就是选项 API ,通过定义 data、methods、computed等属性与方法,来共同处理页面的逻辑。所以当组件变得复杂时,就会导致对应属性的列表也会变长,这就可能导致组件难以阅读或理解。
而 Composition API ,组件会根据逻辑功能来进行组织,一个功能所定义的所有 API 都会放在一起。这样就算项目功能很多,也能快速找到这个功能所用到的所有 API。
在 Options API 中,在处理单个逻辑点的时候,可能需要不断在相关的代码块进行跳转。在composition API 中,单个逻辑点相关代码都会放在同一个函数中,需要修改的时候,在这个函数里就可以了。
在 Options API 混入大量 mixins 的时候,很可能会发生 命名冲突、数据来源不清晰等问题。
composition API 不使用 this,就不会出现 this 指向不明的情况
参考文档:web前端面试 - 面试官系列
Docs