Vue应用的底层原理

本文深入讲解Vue框架的响应式机制,探讨如何通过Object.defineProperty实现数据的监听与更新,揭示数据劫持与订阅发布模式的工作原理。

vue深入响应式原理

  • 讲到Vue的响应式原理,我们可以从它的兼容性说起,Vue不支持IE8以下版本的浏览器,因为Vue是基于 Object.defineProperty 来实现数据响应的,而 Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因;Vue通过Object.defineProperty的 getter/setter 对收集的依赖项进行监听,在属性被访问和修改时通知变化,进而更新视图数据;受现代JavaScript 的限制 (以及废弃 Object.observe),Vue不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让Vue转换它,这样才能让它是响应的。

深入响应式原理:

  1. 当视图模型(VM)中的数据模型(M)发生改变时, 视图(V)就会进行更新
  2. Vue通过watcher将data中的属性全部使用Object.definePropery编程getter和setter,当属性值发生
    改变的时候, 就会触发, 然后wather就会触发, 告诉视图(V)进行重新渲染
    • 数据必须放在data选项中才能进行深入响应式
底层原理:

核心使用的是es5的一个方法,这个方法不支持ie8以及以下

Object.defineProperty(obj,obj.attr,descriptor)
参数:

obj:要在其上定义属性的对象。
prop(=obj.attr):要定义或修改的属性的名称。
descriptor:将被定义或修改的属性描述符。它是一个对象, 这个对象有哪些构成?

  • configurable: 决定了对象的key是否可删除
  • enumerable: 决定了对象是否可遍历(枚举)
  • writeable: 决定了对象的key的value是否可修改
存储器:

get函数 ----起了个名字getter 设置当前对象的key的初始值
set函数 ----起了个名字setter 修改当前对象的key的值

总结:
  1. 什么是深入响应式原理?
    深入响应式原理是利用了数据劫持和订阅发布的模式, 当数据模型发生改变的时候, 视图就会响应的进行更新, 那么深入响应式原理是利用es5的Object.defineProperty中个getter/setter来进行数据的劫持的

    名称解释:
    数据劫持: Object.defineProperty中的getter/setter , 然后在执行watcher
    订阅发布:事件(自定义事件)

  • 订阅: 事件的声明 vm.$on
  • 发布: 事件的触发 vm.$emit
非响应式情况
解释
  • 在vm模型的data外定义的属性, 就是非响应式属性, 非响应式属性, 属性值更新, 视图不会更新
非响应式属性如何变成响应式属性

思维: 将非响应式属性合并到响应式属性身上
解决: 利用了Vue提供的 Vue.set/this.$set(vm.dataattr,prop,value)

Vue.set底层原生是什么?

Object.assign(目标对象,对象1, 对象2,对象3)

来一波整体案例代码解析:

<body>
<div id="app">
<h3> {{msg}} </h3>
<h3> {{num}} </h3>
<button @click = "setHandler">
点击
</button>
</div>
</body>
<script>
Vue.config.silent = true
var vm = new Vue({
el: '#app',
data: {
msg: 'hello vue.js',
list: {
id: 1
}
},
methods: {
setHandler(){
this.$set(this.list,'name','张三')
}
},
watch: {
/* watch中存放的是方法, 方法名就是M(data选项) */
msg(){
console.log('msg改变了')
}
}
})
console.log('====================================');
console.log(vm);
console.log('====================================');
// vm.num = 1000 //非响应式的


/* Vue.set(vm.list,'num',1000)
console.log(vm) */
/* var obj = {
name:'张三'
}
Object.defineProperty(obj,'name',{
//存储器
get(){
//要求必须有返回值
return '三三'
},
set(value){ //value就是修改后的对象的key的value
console.log('set')
console.log('====================================');
console.log(value);
console.log('====================================');
document.querySelector('#app').innerHTML = value
}
// 监听机制
})
obj.name = 'Aaa'
// document.querySelector('#app').innerHTML = obj.name */
</script>

vue双向数据绑定原理

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情.。

1. 效果

数据改 , 视图更
视图改, 数据更

2. 实现

使用v-model实现

3. 缺点

v-model默认绑定value属性, 所以v-model只能在表单使用

4. 原理
1. 为什么数据能直接在视图显示

v-model默认绑定了DOM对象的value属性, 当它初次绑定的时候,
就会触发getter,watcher就会触发, watcher通知Vue生成新的VDOM树,
再通过render函数进行渲染,生成真实DOM

2. 为什么视图修改数据就会修改

当视图修改是, 意味着DOM的value属性值改变,就会触发setter,watcher监听机制就会执行
watcher通知Vue生成新的VDOM树,再通过render函数进行渲染,生成真实DOM

代码示例

<body>
<div id="app">
<input type="text" v-model = "msg">
</div>
</body>

<script>
new Vue({
         el: '#app',
          data: {
                  msg: 'hello vue.js'
                   }
           })
</script>

watch 监听

(使用这个 属性,可以监视 data 中指定数据的变化,然后触发这个 watch 中对应的 function 处理函数该方法可以不用绑定事件)

watch
1. 作用:

用来监听数据的变换, 当数据模型 (data选项 M)发生改变时, watch就会触发

2. 使用

两种用法:

  1. key的value值是一个函数 普通
new Vue({
watch: {
key(value,oldvalue){}
}
})
  1. key的value值是一个对象 高级
new Vue({
watch: {
key: {
deep: true/false 深入监听,
handler(value,oldvalue){} // 监听的处理函数
}
}
})
watch中的key指的就是data选项中key
现在我们来看几个关于监听的案例:
1。首先是在两个输入框输入我们的姓跟名,然后在第三输入框实时输出我们输入的姓和名的全称

解决:

<body>
<div id="app">
<div>
姓: <input type="text" v-model = "firstName">
</div>
<div>
名: <input type="text" v-model = "lastName">
</div>
<div>
全称: <input type="text" v-model = "fullName">
</div>
</div>
</body>
<script>

new Vue({
el: '#app',
data: {
firstName: '',
lastName: '',
fullName: ''
},
watch: {
firstName(val){
this.fullName = val + this.lastName
},
lastName(val){
this.fullName = this.firstName + val
},
fullName(val){
console.log( val )
this.firstName = val.slice(0,1)
this.lastName = val.slice(1)
}
}
})
</script>
computed

在 computed 中,可以定义一些 属性,这些属性,叫做 【计算属性】, 计算属性的本质,就是 一个方法,只不过我们在使用 这些计算属性的时候,是把它们的名称直接当作属性来使用的;并不会把 计算属性当作方法去调用;computed和methods平级

  • computed不会因为页面中调用几次函数去执行几次,只会根据函数执行来调用函数

注意1: 计算属性,在引用的时候,一定不要加 () 去调用,直接把它当作普通 属性去使用就好了;
注意2: 只要 计算属性,这个 function 内部,所用到的 任何 data 中的数据发送了变化,就会立即重新计算 这个 计算属性的值
注意3:计算属性的求值结果,会被缓存起来,方便下次直接使用; 如果 计算属性方法中,所有的任何数据,都没有发生过变化,则不会重新对计算属性求值;

watch、computed和methods之间的对比
  • computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
  • methods方法表示一个具体的操作,主要书写业务逻辑;
  • watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computed和methods的结合体;

  • ** methods: 事件
    ** computed:
    1. 有逻辑 2. 要像变量一样使用 , 这个时候选computed
      ** watch:
    2. 有异步操作 2. 开销较大

<think>我们被要求解析Vue3的底层原理,根据提供的引用材料,我们可以从几个核心方面展开:响应式系统、编译器优化、虚拟DOM的Diff算法(包括静态提升和动态节点标记)、组合式API等。下面将按照这些方面进行详细解析,并确保使用正确的LaTeX格式和引用标识。 ### 1. 响应式系统 Vue3的响应式系统基于ES6的Proxy实现,相比Vue2的Object.defineProperty,Proxy可以监听整个对象,而无需递归遍历每个属性,并且能监听新增属性和数组的变化。响应式系统的主要工作是在读取数据时收集依赖(effect),在修改数据时触发依赖更新。 每个响应式对象都会关联一个依赖管理器(Dep),当执行副作用函数(如组件的渲染函数)时,会触发响应式数据的getter,此时将当前副作用函数收集为依赖。当响应式数据变化时,会触发setter,从而通知所有依赖进行更新。 响应式API包括: - `reactive`: 创建深度响应式对象。 - `ref`: 创建基本类型的响应式引用(内部通过`.value`访问)。 - `computed`: 创建计算属性,基于其他响应式数据派生值。 - `watch`和`watchEffect`: 监听响应式数据的变化并执行副作用。 ### 2. 编译器优化 Vue3的编译器在将模板编译成渲染函数时,进行了多项优化,以提高运行时性能。 #### 静态提升(Static Hoisting) 编译过程中,将模板中的静态节点(即不依赖响应式数据的节点)提升到渲染函数外部。这样,在每次重新渲染时,无需重新创建这些静态节点,而是复用已创建的静态节点。 #### 补丁标志(Patch Flags) 在编译阶段,编译器会分析动态绑定的类型(如class、style、props等),并为每个动态节点打上一个补丁标志(patchFlag),该标志是一个数字,表示动态绑定的类型。这样在运行时,虚拟DOM的Diff算法就可以根据补丁标志有针对性地进行更新,而不需要全量比较。 例如,一个节点只有文本内容是动态的,那么它的补丁标志为`1`(表示`TEXT`更新),在Diff时只需比较文本内容即可。 #### 区块(Block) Vue3引入了Block的概念,一个Block是一个特殊的虚拟节点,它收集了所有包含动态绑定的子节点(称为动态子节点),存储在其`dynamicChildren`数组中。这样在更新时,只需要遍历这个动态子节点数组进行Diff,而无需遍历整个子树,从而大大提升了Diff效率。 Block的形成依赖于编译器和运行时的配合。在创建虚拟节点时,通过`createVNode`和`createBlock`函数,以由内向外的顺序收集动态子节点,并利用一个临时栈来存储动态节点,最终形成一个Block树。 ### 3. 虚拟DOM与Diff算法 Vue3的虚拟DOM Diff算法在Vue2的基础上进行了优化,主要分为5个阶段: 1. **预处理前置节点**:从头部开始比对,遇到相同节点则更新,直到遇到不同节点则停止。 2. **预处理后置节点**:从尾部开始比对,遇到相同节点则更新,直到遇到不同节点则停止。 3. **处理仅有新增节点的情况**:如果旧节点序列已经处理完,而新节点还有剩余,则新增这些节点。 4. **处理仅有卸载节点的情况**:如果新节点序列已经处理完,而旧节点还有剩余,则卸载这些节点。 5. **处理复杂情况**(新增/卸载/移动):对于中间乱序部分,采用最长递增子序列(LIS)算法来最小化节点的移动。 通过上述步骤,Vue3能够高效地更新DOM,尽量减少DOM操作。 ### 4. 组合式API Vue3引入了组合式API(Composition API),通过`setup()`函数来组织组件的逻辑。`setup()`函数在组件实例创建之前执行,接收两个参数:`props`和`context`(包含attrs、slots、emit等)。在`setup()`函数内部,可以使用一系列响应式API(如`ref`、`reactive`)和生命周期钩子(如`onMounted`、`onUpdated`)来定义组件的状态和行为。 组合式API使得逻辑复用更加灵活(通过自定义组合函数),并且可以将相关逻辑组织在一起,提高了代码的可读性和可维护性。 ### 5. 生命周期 Vue3的生命周期钩子与Vue2类似,但有一些变化: - `beforeCreate`和`created`被`setup()`替代(因为`setup()`在组件实例初始化时执行,相当于这两个钩子的合并)。 - 其他钩子前面加上`on`,如`onBeforeMount`、`onMounted`等,并需要在`setup()`中显式导入和调用。 通过上述原理Vue3在性能和开发体验上都有了显著提升[^1][^2][^3][^4]。 §§相关问题§§ 1. Vue3的响应式系统是如何避免Vue2中响应式缺陷的? 2. 请详细解释Vue3中的Block树是如何构建的? 3. 为什么组合式API更适合大型项目? 4. Vue3的编译器优化具体如何提升性能? </think>### Vue 3 底层原理详细解析 Vue 3 的底层原理围绕**响应式系统**、**编译器优化**、**虚拟 DOM 的 Diff 算法**和**组合式 API**四大核心设计展开,以下是详细解析: --- #### 1. **响应式系统:Proxy 替代 defineProperty** Vue 3 使用 `Proxy` 实现响应式数据绑定,解决了 Vue 2 中 `Object.defineProperty` 的局限性: - **深度监听**:`Proxy` 自动递归监听嵌套对象,无需手动遍历属性[^1]。 - **动态属性**:新增或删除属性时无需 `Vue.set/$delete`,直接触发更新。 - **性能优化**:仅访问属性时才触发依赖收集,避免不必要的递归开销。 **实现原理**: ```typescript const reactive = (target) => { return new Proxy(target, { get(obj, key) { track(obj, key); // 依赖收集 return Reflect.get(obj, key); }, set(obj, key, value) { Reflect.set(obj, key, value); trigger(obj, key); // 触发更新 } }); }; ``` --- #### 2. **编译器优化:静态提升与动态标记** 编译器将模板编译为渲染函数时,通过以下策略优化性能: - **静态提升(Hoist Static)**: 将静态节点(无动态绑定的 DOM)提取到渲染函数外部,避免重复创建[^4]。 - **补丁标志(Patch Flag)**: 动态节点(如 `:class`、`v-model`)被标记为数字(如 `1` 表示文本动态),Diff 时仅比对动态部分[^4]。 - **区块(Block)**: 父节点收集动态子节点到 `dynamicChildren` 数组,跳过静态子树遍历[^4]。 **示例**: ```html <!-- 编译前 --> <div> <span>静态文本</span> <!-- 被提升 --> <span>{{ dynamicText }}</span> <!-- 标记 PatchFlag=1 --> </div> ``` --- #### 3. **虚拟 DOM Diff:五阶段高效比对** Vue 3 的 Diff 算法分为 5 个阶段,时间复杂度接近 $O(n)$[^2]: 1. **预处理前置节点**:头对头比对相同节点。 2. **预处理后置节点**:尾对尾比对相同节点。 3. **处理纯新增节点**:新序列有剩余则添加。 4. **处理纯卸载节点**:旧序列有剩余则删除。 5. **复杂序列处理**:通过**最长递增子序列算法**(LIS)最小化移动操作。 **关键优化**: - 仅处理动态节点(`dynamicChildren`),跳过静态子树。 - 使用 `key` 复用节点,减少 DOM 操作。 --- #### 4. **组合式 API:逻辑复用与组织** 组合式 API 通过 `setup()` 函数集中管理逻辑: - **响应式状态**:`ref`(基本类型)和 `reactive`(对象)创建响应式数据。 - **生命周期钩子**:`onMounted`、`onUpdated` 等替代 `created`/`mounted`[^3]。 - **逻辑复用**:自定义 Hook 封装可复用逻辑(如 `useFetch`)。 **示例**: ```typescript import { ref, onMounted } from 'vue'; export default { setup() { const count = ref(0); onMounted(() => console.log("组件挂载")); return { count }; } } ``` --- #### 5. **性能对比 Vue 2** | 特性 | Vue 2 | Vue 3 优化点 | |--------------|---------------------|----------------------------------| | 响应式 | `defineProperty` | `Proxy` 自动深度监听 | | 虚拟 DOM | 全量 Diff | 动态节点标记 + 静态提升 | | 包体积 | 完整运行时 | Tree-shaking 按需引入 | | 编译输出 | 无优化标志 | Patch Flag + Block Tree | --- ### 总结 Vue 3 通过 `Proxy` 响应式、编译时静态标记、虚拟 DOM 的 Block 结构和组合式 API,实现了**性能翻倍**和**开发体验升级**。其核心思想是**按需更新**(靶向更新)和**逻辑复用**,解决了大型应用的性能瓶颈与代码组织问题[^1][^2][^3][^4]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值