概述
在MVVM中,改变数据就意味着改变视图,这意味着我们不再需要在模型和视图之间加入控制器,这种现代化的设计模式使开发变得更清晰简洁。然而对于数据双向绑定的实现,各大流行框架都有自己的实现,其中VueJS的主要黑魔法就是利用了ES5的一个API,Object.defineProperty。
监听器模式
在介绍Object.defineProperty在数据视图绑定的应用之前,需要先学习一个设计模式,监听器模式(也叫观察者模式,或者订阅-发布模式),这并不是一个什么高大上的东西,实际上给DOM添加事件就是一种监听器模式,它的简单实现如下。
1 | const store = (function() { |
可以看到,store是一个IIFE执行的返回结果,其中_value用于存储值,_fns用于存储注册的响应函数,当setValue方法被执行时,会自动触发所有的已经注册的方法,这些方法都将接收到store中存储的值作为参数,这个实现使用起来也非常方便。
1 | store.subscribe(v => console.log(v)); |
这里注册两个了响应函数,分别是输出value和value + 3的结果,测试可以发现运行与预期一致。以上就是一个简单的监听器模式和使用方法,可以看到它确实和DOM事件的添加很像,此外,我们还可以在添加时就保存函数变量(不使用匿名函数,使用函数表达式),这样还可以通过unsubscribe来移除响应函数。
基本实现
现在已经对监听器模式有了一个大致的理解了,那它如何配合Object.defineProperty一起使用呢?
Object.defineProperty用于直接在一个对象上定义属性,其使用方法如下。
1 | Object.defineProperty(obj, prop, descriptor); |
其中obj表示需要被定义新属性的对象,prop是一个字符串,表示新属性,descriptor用于描述相关行为内容,这里我们只关心set和get,具体可查看参考1,来看如下代码。
1 | <div id="app">haha</div> |
1 | let data = {}; |
1 | data.user = 'Ouyang'; |
运行如上代码,会发现当我们执行完data.user = ‘Ouyang’这行语句之后,视图也自动更新了。
为了能让这种方式更灵活,可以将它和监听器模式结合在一起使用。
1 | const configProperty = function(obj, prop, fn) { |
configProperty接收三个参数,第一个是将要被设置属性的对象,第二个是属性名,第三个注册了当属性改变时的响应函数。
configPropert的使用方法也很简单。
1 | let obj = {}; |
对一个对象使用configProperty配置完属性之后,在注册响应的监听器,此时,只要改属性的值被设置(且与之前的值不同),则触发注册的注册的函数。
目前的这种实现不能算是监听器模式,因为注册函数只能在一开始就配置属性时就确定(也就是一开始就要确定configProperty的第三个参数),不过要写成监听器模式只需稍加扩展,将注册监听器的方法独立出来即可(需要用到闭包)。
总结
整体而言,这种设计是的视图和模型的之间的关系更明确了,用学术一点的说法就是F(Model) = View,对于这样的设计而言,只要对模型进行修改,视图自动就应该被更新,而不需要多余的操作。
当然,监听器模式配合Object.defineProperty也只是一种实现方式而已,对于模型和视图的处理,前面文章中提到过的redux也是一种非常好而且很流行的实现。