一、Vue 中的双向绑定流程
1、简单流程:
① new Vue()
首先执行初始化,对data
执行响应化处理,这个过程发生Observer
中【劫持监听所有数据】② 同时对模板执行编译,找到其中动态绑定的数据,从
data
中获取并初始化视图,这个过程发生在Compile
中【解析模板中的指令】③ 同时定义⼀个更新函数和
Watcher
,将来对应数据变化时Watcher
会调用更新函数④ 由于
data
的某个key
在⼀个视图中可能出现多次,所以每个key
都需要⼀个管家Dep
来管理多个Watcher
⑤ 将来data中数据⼀旦发生变化,会首先找到对应的
Dep
,通知所有Watcher
执行更新函数
2、细化一点的流程:
① 使用递归和【Object.defineProperty】的方式,对所有属性进行劫持监听:
其中,getter函数和setter函数是数据监听的核心。可以获取属性值并监听数据的变化【这个过程发生在Observer中:对data执行响应化处理】
②【HTML模板解析】,将数据更新到DOM上:
匹配插值语法获取到属性名,并通过链式调用的方式获取属性对应的值。将插值语法中的内容替换成属性值,最后将文档碎片应用到DOM上【这个过程发生在Compile中】
以上操作完成了视图的初始化,也就是数据应用到视图上。接着就是要实现,当数据data发生变化的时候,视图DOM也会随之改变。
③ 使用【发布者-订阅者模式】,通过这种模式当对应数据变化时,订阅者就知道将来如何更新自己。
定义一个依赖函数,用于收集和通知订阅者;再定义一个Watcher函数,当订阅数据发生变化时Watcher 会调用更新函数。
在替换文档碎片时,创建订阅者Watcher实例,告诉订阅者如何更新,往后进行回调的时候,订阅者就知道如何更新自己了。
在数据劫持之前创建依赖Dependency实例(可以理解为一个管家),并在数据劫持调用getter函数的时候,将订阅者加入依赖实例的数组中(也就是添加订阅者)。然后在数据劫持修改数据的时候(也就是调用setter函数时),通知订阅者去更新数据。以上操作完成了对文档的绑定。
以上操作完成了数据改变视图的逻辑。接下来就是要实现用视图改变数据。
④ 实现用视图更新数据:
通过addEventListener监听输入框节点的input,然后把获取到的值【赋值】给属性名就可以了,也就是给Vue实例上的数据赋值。
以上操作就完成了双向数据绑定。
3、总结版:
点题收敛
Vue的数据绑定机制(响应式)主要是通过数据劫持 Object.defineProperty() 和发布-订阅模式来实现的。当数据发生变化时,会自动更新视图,并通过虚拟 DOM 对比算法来提高性能。
它的实现逻辑是这样子的:
在初始化的时候,通过 Object.defineProperty 的方式,递归劫持所有的属性并手动添加 getter 函数和 setter 函数。
当触发 getter 函数的时候,会进行依赖的收集,也就是 Watcher 对象订阅响应式数据对象的变化。一旦有属性发生变化,也就是触发 setter 函数的时候,那 Watcher 对象会接收到变化的通知,并更新组件的视图。
Vue的数据绑定机制实现步骤大概是这样子的:
涉及到三个比较重要的对象:Observer / Watcher / Dep
① 首先定义一个监听者 Oberver:
使用递归的方式对所有的属性进行劫持监听,也就是使用 Object.defineProperty 的方式。其中,getter 函数和 setter 函数是数据监听的核心,可以获取属性值并监听数据的变化。
在 Vue 中,每个组件实例都有一个对应的响应式数据对象。当数据发生变化时会自动更新视图。这个响应式数据对象通过数据劫持的方式实现,即通过 Object.defineProperty 方法来劫持数据的getter 和 setter 方法,当数据被读取或修改时,就会触发 getter 或 setter 方法,从而实现数据的监控和响应。
② 其次,定义一个Dep对象,用来收集和通知订阅者:
当数据劫持触发 getter函数的时候,会把订阅者加入 Dep 实例数组中;一旦有属性发生变化,也就是触发 setter 函数的时候,就会调用 Dep 对象的 notify 方法去通知订阅者更新。
在 Vue 中,每个组件实例都有一个对应的 Watcher 对象,Watcher 对象订阅响应式数据对象的变化。当响应式数据对象发生变化时,Watcher 对象会接收到通知,并更新组件的视图。Watcher对象通过发布/订阅模式实现,即 Watcher 对象订阅响应式数据对象的变化,响应式数据对象在变化时发布通知,通知订阅者(Watcher对象)进行更新。
③ 同时,定义一个订阅者 Watcher 对象:
来接受属性变化的通知,并执行 update 方法去更新组件的视图。
当一个组件的数据发生变化时,Vue会通过虚拟DOM对比算法来找到变化的部分,然后更新对应的DOM节点。由于虚拟DOM对比算法只会更新变化的部分因此可以有效地提高性能。
④ 最后就是实现一个HTML模板解析器Compile:
可以扫描和解析每个节点的相关指令。模板编译过程中的指令和数据绑定都会生成一个Watcher实例,实例中的watch属性也会生成Watcher实例,用于订阅数据变化,绑定update函数,Watcher实例在触发回调的时候会对DOM节点值nodeValue进行赋值操作从而实现视图的更新。(组件执行render函数,生成一个虚拟DOM树,此时Vue会对新老虚拟DOM树进行Diff,查找出需要操作的真实DOM并对齐进行更新)
⑤ 以上步骤就可以实现数据更新视图,而视图更新数据是相对比较简单的,只要通过事件监听addEventListener,将输入的值赋值给Vue实例上的数据即可。
2、Vue.js 双向数据绑定的具体实现代码:
① 简洁版
Vue.js 双向数据绑定的具体实现代码(简洁版)_小草莓蹦蹦跳的博客-CSDN博客
② 完善版
Vue.js 双向数据绑定的具体实现代码(完善版)_小草莓蹦蹦跳的博客-CSDN博客
3、总结
实现MVVM主要包含两个方面,数据变化更新视图,视图变化更新数据。
关键点在于 data 如何更新view,因为 view 更新 data 其实可以通过事件监听即可,比如 input 标签监听 'input' 事件就可以实现了。
而 数据更新视图 的重点是如何知道数据变了,只要知道数据变了,那么接下去的事都好处理。
如何知道数据变了,就是 通过Object.defineProperty( )对属性设置一个setter函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。
这是一个简单的【数据变化更新视图,视图变化更新数据】的例子:
简单实现“数据变化更新视图,视图变化更新数据”(MVVM)_小草莓蹦蹦跳的博客-CSDN博客
4、Object.defineProperty()
定义:
Object.defineProperty(参数1,参数2,参数3)
① 参数1:该对象(obj)
② 参数2:要定义或修改的对象的属性名
③ 参数3:属性描述符。
作用:这个方法会直接在一个对象上定义一个新属性或者修改对象上的现有属性,并返回该对象
例子: 使用Object.defineProperty()修改对象上的某个现有属性
<script>
function observer (data) {
// 判断如果没有传值,或者传的值不是一个对象,就直接return
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(item => {
observerProperty (data, item, data[item])
})
}
function observerProperty (target, key, value) {
observer (value)
Object.defineProperty(target, key, {
get () {
return value;
}
set (val) {
value = val
}
}
}
// 这样就会对obj进行一个劫持,一旦改变obj内的属性,就返回相对应的数据,比如对obj的name进行了改变,observer()函数可能会返回一句obj的name已经改变为...
// observer (obj)
let obj = {
id:1,
name:'草莓',
age: 20
}
</script>
5、v-model
总结:
1、双向绑定靠的是指令 v-model,可以绑定一个动态值到视图上,同时修改视图能够改变数据对应的值。而能修改视图的元素就只有表单组件;
v-model 是 v-bind 事件绑定 和 v-on 事件监听 的语法糖:
如下 v-bind 和 v-on 实现了双向数据绑定。
<input type="text" v-bind:value="value" v-on:input="value = $event.target.value" />
<!-- 等同于 -->
<input type="text" :value="value" @input="value = $event.target.value" />
<!-- v-model是v-bind和v-on的语法糖,即v-model算是v-band和v-on的简洁写法。 -->
<input type="text" v-model="value">
对于普通元素而言,v-model 只能使用在表单元素中,因为其他元素中并不支持双向绑定,想div/p标签等,能修改视图的才能支持双向绑定;
表单元素中的 v-model ,内部会根据标签的不同解析出不同的语法,默认是 value + input,这里还有额外的处理逻辑,也就是中文处理的问题。
2、v-model 绑定在组件上,那就相当于 value + input 的语法糖。组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件;
<!-- 父组件 -->
<!-- 在组件中使用 -->
<Child v-model="value"></Child>
<!-- 等同于 -->
<Child v-bind:value="value" v-on:input="input"></Child>
<!-- 等同于 -->
<Child :value="value" @input="input"></Child>
<script>
methods: {
input(data) {
console.log(data);
this.value = data;
}
}
<script>
<!-- 子组件 -->
<input type="text" :value="value" @input="input" />
<script>
export default {
// 1. 接受父级传递的值
props: {
value: {
type: String,
default: ""
}
},
methods: {
input(event) {
this.$emit("input", event.target.value);
}
}
};
<script>
Vue 提供了 model 选项,对 props 和 event 的名字进行修改。