双向数据绑定与mvvm
本文是基于vue的理解
目录
- 双向绑定的概念
- 双向绑定实现的原理
- 发布者-订阅者模式
- 数据劫持模式
- vue中的mvvm的理解
双向绑定的概念
直接点说,就是初始数据(接口拉取或本地定义的data)变化时视图要更新;在视图上进行了某种操作(如input),初始数据也需要被同步更新。
双向绑定是在单向绑定的基础上扩展的,单向绑定就是初始数据变化,触发视图更新。然后在视图上做一些监听(如input)后,去触发更新初始数据,就形成了双向绑定,例如vue的v-model
,我们也知道v-model
的本质是:value
与@input
的组合。
观察者模式
发布者-订阅者模式
三要素:
- 订阅
- 发布
- 取消订阅
那么一个观察者看起来是这样的
const pubSub = {
pub(topic, data) {},
sub(top, func) {},
unSub(func) {}
}
const toEat = () => {}
// 订阅
pubSub.sub('eat', toEat)
// 取消订阅
pubSub.unSub(toEat)
// 发布
pubSub.pub('eat', 'eat big')
看起来很熟悉有木有,像极了addEventListener
。此处参考深入理解JavaScript系列(32):设计模式之观察者模式
数据劫持模式
通过Object.defineProperty实现
当前版本Vue的响应式核心便是通过Object.defineProperty改写每个属性的set从而监听属性,然后在get中添加订阅实现的。
如果你看过vue的源码,你应该知道在vue的双向绑定中,有几个概念是需要理解的
- 监听器Observer
- 订阅者Watcher
- 用来统一管理订阅与发布操作的Dep
- 编译器Compile,编译模版(属性、事件与指令)
vue双向绑定的工作流程:
初始化时,监听(Observer)data
里的属性,Dep收集属性的订阅器(Watcher),然后解析模版,得到属性、事件与指令后,初始化数据到视图并添加该属性到订阅器。随后,属性赋值操作,会触发监听器Observer去Dep里执行与该属性有关的更新函数update。
我们可以实现一个很简单的demo,源码请戳,文件结构如下:
.
├── README.md
├── pubSub.html
└── src
├── compile.js // 初始化时模版编译,并添加订阅者,即new Watcher
├── dep.js // 收集订阅与集中发布
├── index.html // 首页
├── mvvm.js // 构造函数
├── observer.js // 初始化时,最先监听data,当属性变化时,触发订阅者更新
└── watcher.js // 订阅者的更新与添加
我们的使用方法如下
<div id="app">
{{ name }}
<div>
<input type="text" :value="name" @input="changeName">
</div>
</div>
<script>
const root = new Mvvm({
el: '#app',
data: {
name: '123'
},
methods: {
changeName(e) {
this.name = e.target.value
}
}
})
</script>
demo如下
在源码的compile
中,我只对当前例子的text
和Element属性中的:和@
做解析处理,以便我们能知道mvvm双向绑定的原理,源码部分如下
compile(node) {
const attrs = node.attributes
Array.from(attrs).forEach(attr => {
let attrName = attr.name
let fn = this.$vm.$options.methods[attr.value]
// 这里只处理:和@
if (/:|@/.test(attrName)) {
if (/@/.test(attrName)) {
// 事件绑定的简写
node.addEventListener(attrName.substring(1), fn.bind(this.$vm), false)
} else {
console.log('bind value', attrName)
this.compileModel(node, attr.value)
}
// 执行完后 移除掉
node.removeAttribute(attrName)
}
})
}
最后再强化一遍理解:我们其实要明白,观察者模式,只有在订阅者存在的情况下才有意义,所以,我们必须在初始化的时候,收集完所有的订阅者,也就是说,在compile的时候,Dep到所有的Watcher实例,后续的数据变化, 才会触发该实例的更新操作。
参考:
vue中的mvvm的理解
今天又在网上查mvc
与mvvm
的相关概念,答案太多,看不太清,以至于我到此刻都还不是很明白每一个部分应该干什么事情。因为,貌似在前端领域,mvc的理解与后端是有很大差异的吧。
我们还是直接用vue这个框架来理解mvvm,再去看mvc。
我们都知道
- M是model,即数据结构模型,用来存储视图需要的属性的
- V是view,做属性,事件的绑定与监听
- VM是ViewModel,即view与model之间的桥梁,用来处理视图与数据之间的逻辑
在vue中,拿一个单文件组件示例,template
就是view层,data
就是model层,Vue抽象的双向绑定及组件script
中的其它方法(业务逻辑)就是viewModel了。
我们可以参考wiki中对mvvm的定义来论证我的观点:
MVVM的视图模型是一个值转换器,这意味着视图模型负责从模型中暴露(转换)数据对象,以便轻松管理和呈现对象。在这方面,视图模型比视图做得更多,并且处理大部分视图的显示逻辑。
在查资料的过程中又看到一句话似乎点通了我对网上关于mvc的看法不一致的理解,其大意是后端和前端中的mvc是不一样的,后端中的mvc中的c即代表了前端整个mvc。原文请查看这篇文章的评论,其中@刘宇清
的说法我比较认同。
其实,我们只需要搞清楚mvc中它们自己该干什么事情就好了,在前端里:model是原始数据,view就是展示这些数据的,至于怎么展示,数据需要怎样转换,以及视图与原始数据之间的交互(表单提交),是controller该干的事情。
在mvvm中,vm很显然充当了controller的职责。
mvvm的核心便是数据绑定
,依旧是参考wiki:
声明性数据和命令绑定隐含在MVVM模式中。在Microsoft解决方案堆中,绑定器是一种名为XAML的标记语言。绑定器使开发人员免于被迫编写样板式逻辑来同步视图模型和视图。在微软的堆之外实现时,声明性数据绑定技术的出现是实现该模式的一个关键因素。
很显然,理解数据绑定对于理解mvvm一类的框架是很有帮助的。
最后,欢迎一起交流。