vue双向数据绑定
# vue 双向数据绑定
概念:数据变化更新视图,视图变化更新数据
原理:vue 的双向绑定是由数据劫持结合发布者-订阅者模式实现的
# 实现双向绑定流程
- 监听器 Observer: new Vue()首先执行初始化,对 data 执行响应化处理,这个过程发生 Observe 中
用来劫持并监听所有属性,如果有变动的,就通知订阅者。
- 解析器 Compile:同时对模板执行编译,找到其中动态绑定的数据,从 data 中获取并初始化视图,这个过程发生在 Compile 中
可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
- 订阅者 Watcher:同时定义⼀个更新函数和 Watcher,将来对应数据变化时 Watcher 会调用更新函数
可以收到属性的变化通知并执行相应的函数,从而更新视图。
- 依赖收集器 Dep:由于 data 的某个 key 在⼀个视图中可能出现多次,所以每个 key 都需要⼀个管家 Dep 来管理多个 Watcher, 将来 data 中数据⼀旦发生变化,会首先找到对应的 Dep,通知所有 Watcher 执行更新函数
# 原理图
# 数据变化更新视图
数据劫持:
vue2.0 通过 Object.defineProperty()来劫持对象属性的 setter 和 getter 操作,在数据变动时发布消息给订阅者,触发相应的监听回调。
vue3.0 通过 Proxy 来劫持各个属性的 setter 和 getter 操作,在数据变动时发布消息给订阅者,触发相应的监听回调
# 视图变化更新数据
事件监听
input 监听 input 事件;
checkbox 、 radio、select 监听 change 事件;
# 5.2 vue 监听数组变化三部曲
第一步:先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。
第二步:对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。
第三步:把需要被拦截的 Array 类型的数据原型指向改造后原型。
# 5.1 直接给一个数组项赋值,Vue 能检测到变化吗?
由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:
当你利用索引直接设置一个数组项时,例如: vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如: vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue);
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue);
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue);
2
3
4
5
6
为了解决第二个问题,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
# Proxy 和 Object.defineProperty 的对比
Proxy 是直接代理对象;而 Object.defineProperty 只能劫持对象的属性,
Proxy 能监听对象的新增和删除操作;Object.defineProperty 不能监听对象的新增和删除操作,通过 Vue.set()和 Vue.delete 来实现响应式的。
Proxy 可以直接监听数组的变化;Object.defineProperty 本身是有监控数组下标变化的能力的,只是在 Vue 的实现中,从性能/体验的性价比考虑,没有使用
Proxy 支持 13 种拦截操作,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的。
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的;而 Object.defineProperty 只能遍历对象属性直接修改;
Proxy 兼容性差;Object.defineProperty 兼容性好,支持 IE9,
Proxy 有性能问题但是有新标准性能红利,从长远来看,JS 引擎会继续优化 Proxy
提示
注意:Object.defineProperty 本身是可以监控到数组下标的变化的,但是在 Vue 中,从性能/体验的性价比考虑(如果数组中有成千上万的数据),尤大大就弃用了这个特性 Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。数组的索引也是属性,所以我们是可以监听到数组元素的变化的
# 双向绑定方法
实现数据绑定的做法有大致如下几种:
发布者-订阅者模式(backbone.js) 脏值检查(angular.js) 数据劫持(vue.js)
# vue 单向数据流
# 21. 怎样理解 Vue 的单向数据流?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个 prop 的情形 :
- 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function() {
return {
counter: this.initialCounter
}
}
2
3
4
5
6
- 这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
normalizedSize: function() {
return this.size.trim().toLowerCase()
}
}
2
3
4
5
6
# v-model 解析
v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
语法糖,简单来说就是『便捷写法』。
v-model 是双向绑定:你可以用 v-model 指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。
v-model 是单向数据流
单项数据流:子组件不能改变父组件传递给它的 prop 属性,推荐的做法是它抛出事件,通知父组件自行改变绑定的值。
『单向数据流』总结起来其实也就 8 个字:『数据向下,事件向上』。
# v-model 原理
# 自定义组件支持 v-model
在定义 vue 组件时,你可以提供一个 model 属性,用来定义该组件以何种方式支持 v-model。
model 属性本身是有默认值的:
// 默认的 model 属性
export default {
model: {
prop: "value", // 代表 v-model 绑定的prop名
event: "input", // 代码 v-model 通知父组件更新属性的事件名
},
};
2
3
4
5
6
7
此时的 v-model="foo"
就完全等价于 :value="foo"
加上 @input="foo = $event"
也可以自定义:
// 默认的 model 属性
export default {
model: {
prop: "ame",
event: "zard",
},
};
2
3
4
5
6
7
此时的v-model="foo"
就等价于 :ame="foo"
加上 @zard="foo = $event"
。
自定义组件:
<template>
<div>
我们是TI{{ ame }}冠军
<el-button @click="playDota2(1)">加</el-button>
<el-button @click="playDota2(-1)">减</el-button>
</div>
</template>
<script>
export default {
props: {
ame: {
type: Number,
default: 8,
},
},
model: {
// 自定义v-model的格式
prop: "ame", // 代表 v-model 绑定的prop名
event: "zard", // 代码 v-model 通知父组件更新属性的事件名
},
methods: {
playDota2(step) {
const newYear = this.ame + step;
this.$emit("zard", newYear);
},
},
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
使用该组件:
<template>
<dota v-model="ti"></dota>
</template>
<script>
export default {
data() {
return {
ti: 8,
};
},
};
</script>
2
3
4
5
6
7
8
9
10
11
12
参考:https://juejin.cn/post/7049135444310622245 (opens new window)