前言
单向绑定非常简单,就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。
有单向绑定,就有双向绑定。如果用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。
什么情况下用户可以更新View呢?填写表单就是一个最直接的例子。当用户填写表单时,View的状态就被更新了,如果此时MVVM框架可以自动更新Model的状态,那就相当于我们把Model和View做了双向绑定。
双向绑定在MVVM模式中发挥着及其重要的作用,它将视图与数据绑定起来,让我们得以关注于前后端交互的数据变动,而不必过度费心于页面的刷新上。
MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。
一些关键解释如下。
模型
- 模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
视图
- 就像在MVC和MVP模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。
视图模型
- 视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器,也没有MVP模式的presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
绑定器
- 声明性数据和命令绑定隐含在MVVM模式中。绑定器使开发人员免于被迫编写样板式逻辑来同步视图模型和视图。在微软的堆之外实现时,声明性数据绑定技术的出现是实现该模式的一个关键因素。
数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更
数据劫持: vue3.0之前的版本是采用数据劫持结合发布者-订阅者模式的方式,通过
Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
接下来,我将基于Object.defineProperty()和观察者模式,一步步实现双向绑定,构建一个简单的MVVM模式的雏形。如果还不了解Object.defineProperty()和观察者模式原理,建议可以先看看我之前的两篇博文。
双向绑定基础原理——Object.defineProperty()的使用 https://blog.csdn.net/qq_41996454/article/details/107988996
设计模式之观察者模式——Js实现 https://blog.csdn.net/qq_41996454/article/details/108042475
参考资料
《用ES6的class模仿Vue写一个双向绑定》 https://www.jianshu.com/p/78058c7922bf
剖析Vue原理&实现双向绑定MVVM
vue的双向绑定原理及实现
《Vue原理解析(八):一起搞明白令人头疼的diff算法》https://blog.csdn.net/u011199186/article/details/103668263
processon在线画图 https://www.processon.com/diagrams
初级版本
实现publisher
这个publisher实现的时候,要注意一下几点:
①监听data(对data中每个属性都使用Object.defineProperty加上setter和getter)
②对所有订阅者发布data的更新消息(调用订阅者的公共接口receiveTips)
那么监听到变化之后就是怎么通知订阅者了,所以接下来我们需要实现一个消息订阅器,很简单,维护一个数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法
下面是封装成一个MVVM形式的样子。
let myMvvm = {
$data: {
data01: "test01",
data02: "test02",
data03: "test03",
},
// 指定元素
$el: {}
};
// 对data中每个属性都使用Object.defineProperty加上 setter和getter
myMvvm._initPublisher = function (data = this.$data) {
let that = this;
Object.keys(data).forEach((key) => {
let value = data[key];
// 设置
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log("这是get()方法");
return value;
},
set: function (newVal) {
if (value !== newVal) {
console.log("拦截到数据变化" + value + "---->" + newVal);
value = newVal;
}
},
});
});
console.log(that);
};
myMvvm._initPublisher();
myMvvm.$data.data01 = 1;
console.log(myMvvm.$data.data01);
当然,从简单的方式下入手,我们可以先不用封装成一个MVVM对象的样子。
// 初始化发布者
initPublisher = function (data, subCenter) {
Object.keys(data).forEach((key) => {
let value = data[key];
// 设置
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log("这是get()方法");
return value;
},
set: function (newVal) {
if (value !== newVal) {
console.log("拦截到数据变化" + value + "---->" + newVal);
subCenter.notify(key, value, newVal)
value = newVal;
}
},
});
});
};
let data = {
data01: "test01",
data02: "test02",
data03: "test03",
};
// --------------初始化发布者-------------
initPublisher(data, mySubCenter)