动态数据绑定之监听对象变化
---恢复内容开始---
动态数据绑定是MVVM框架中最基础的的一个功能,简单描述就是:将数据和视图进行绑定,当数据发生改变时,视图随之改变,更深层次一点,数据绑定包括单向数据绑定和双向数据绑定。
本文从数据绑定中的问题出发,一步一步的来实现这个功能。
本文的所有的源代码地址: 点击此处查看源代码
问题一
给定任意一个对象,如何监听其属性的读取与变化?也就是说,如何知道程序访问了对象的哪个属性,又改变了哪个属性?
举个例子:
let app = new Observer({name: 'liujianhuan',company: 'Qihoo 360',address: 'Chaoyang, Beijing'
})//要实现的结果如下 app.data.name //你访问了name app.data.company //你访问了company app.data.address = 'Beijing' //你设置了address, 新的值为 Beijing
实现这样的一个Observer并不难,在此我们暂且不考虑数组的情况,只针对传入的参数为对象。如果对ES6和ES5都熟悉的话,可以立刻想到针对上述场景,可以有两种的实现方式:
- 采用ES6中的proxy,对目标对象的属性进行拦截处理
- 采用ES5中的defineProperty,为目标对象的属性添加setter和getter
接下来首先采用ES6中的proxy方法实现上述场景,首先从阮一峰老师的《es6入门标准中》摘录:
Proxy可以理解成在目标对象前架设一“拦截”层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以称为“代理器”。
上边的话读完后应该和没读一样,放出来也只是用来装一下的。下边直接用简单的例子来说明:
ES6原生提供Proxy
构造函数,用于生成Proxy
实例。
var proxy = new Proxy(target,handler);
var proxy = new Proxy(target,handler);
Proxy
对象的所有用法都是上面的形式,不同的只是handler
参数的写法。其中new Proxy()
表示生成的一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
下面来一个拦截读取属性行为的例子:
var proxy = new Proxy({},{get:function(target,property){ return 35; } }); proxy.time;//35 proxy.name;//35 proxy.title;//35
上面代码中,作为构造函数,Proxy
接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy
的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get
方法,用来拦截对目标对象属性的访问请求。get
方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。
听话分割线出来了,以上内容摘自《ES6标准入门(第二版)》,特此声明!
看了上述的代码之后,我想也应该不用再太多的介绍了,直接上针对问题一的代码:
function Observer(data){return new Proxy(data, { get: function(target, key){ if(key in target){ console.log('你访问了' + key); return target[key]; }else{ throw new Error('key does not exist') } }, set: function(target, key, newVal){ console.log('你设置了' + key); console.log('新的' + key + '=' + newVal); target[key] = newVal; } }) } let app = new Observer({ name: 'liujianhuan', company: 'Qihoo 360', address: 'Chaoyang, Beijing' })
测试结果如下图:
如上图结果所示,上述代码完美的实现了问题一中所提到的监听对象属性变化,但是深入思考就会发现,上述代码还是有问题的,因此,引出来问题二。
问题二
如果传入的参数对象是一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办?
举个例子:
let app = new Observer({basicInfo: {name: 'liujianhuan',age: 25},company: 'Qihoo 360',address: 'Chaoyang, Beijing' }) //要实现的结果如下 app.data.basicInfo.name //你访问了basicInfo,你访问了name
首先利用问题一中的代码进行测试:
从结果可以看到并不能解决问题,到这里也许有人觉得只要在代码中加上这样的一段代码即可:
for(let key in data){if(data.hasOwnProperty(key) && typeof data[key] === 'object'){ new Observer(data[key]); } }
事实上是这种方式是无效的,读者可自行测试。究其原因,ES6中的proxy方式是通过Proxy构造函数来new一个实例,此实例代理拦截目标对象的操作,所以对于深层递归new出来的子对象实例我们是无法操作的,所以这种方法无效。
一步一步写的现在是不是觉得人生好无趣,好不容易写了这么多却发现行不通啊。这时候先上一碗热鸡汤,人生的每一步都是我们应该走的,因为它会给我们不同的经历,让我们更坚韧、更强大。 此路不通,那就只能再回首走开篇提到的第二条路了,不过这时候也应该上一碗毒鸡汤,所谓,码农之路就是,山重水复疑无路,柳暗花明又一坑。
第二种方法是采用ES5中的defineProperty,为目标对象的属性添加setter和getter。关于defineProperty的基本知识这里不再赘述,有不清楚的地方可以自行翻阅权威书籍,比如红宝书。直接上代码,下边的代码涵盖了问题一和二。
function Observer (data) {//暂不考虑数组 this.data = data; this.makeObserver(data); } Observer.prototype.setterAndGetter = function (key, val) { //此为问题一的要点 Object.defineProperty(this.data, key, { enumerable: true, configurable: true, get: function(){ console.log('你访问了' + key); return val; }, set: function(newVal){ console.log('你设置了' + key); console.log('新的' + key + '=' + newVal); val = newVal; } }) } Observer.prototype.makeObserver = function (obj) { let val; //此为问题二的要点 for(let key in obj){ if(obj.hasOwnProperty(key)){ val = obj[key]; //深度遍历 if(typeof val === 'object'){ new Observer(val); } } this.setterAndGetter(key, val); } } //测试 let app = new Observer({ basicInfo: { name: 'liujianhuan', age: 25 }, company: 'Qihoo 360', address: 'Chaoyang, Beijing'
})
测试结果如下图:
看到这样的结果是不是很开心呢,同时解决了问题一和问题二,perfect。如果你这样想了,那就得回想一下毒鸡汤了,生活中不是缺少坑,是缺少发现坑的眼睛,直到被你柳暗花明之后踩到。。请继续看问题三。
问题三
如果设置新的值是一个对象的话,新设置的对象的属性是否能继续响应getter和setter呢?
举个例子:
let app = new Observer({basicInfo: {name: 'liujianhuan',age: 25},company: 'Qihoo 360',address: 'Chaoyang, Beijing' }) //要实现的结果如下 app.data.basicInfo = {like: 'NBA'}//你设置了basicInfo,新的basicInfo为{like: 'NBA'} app.data.basicInfo.like //你访问了basicInfo,你访问了like
采用问题二中的代码进行测试:
看到了吧,如果设置新的值是一个对象的话,新设置的对象的属性不能继续响应getter和setter。不过代码写到这里,这个问题应该是非常容易的就可以解决了,那就是直接在setter中添加如下代码:
//如果newval是对象的话
if(typeof newVal === 'object'){ new Observer(val); }
测试结果如下:
至此,我们已经较为完整的了实现了针对对象变化的数据监听,由于数组的操作方法比较多,所以针对数组的变化监听待后续完善,接下来我们针对上述代码继续增强完善。
完善点一
考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做一些特定的事情,而不是每一次只能打印出来一些信息,所以,我们需要支持传入回调函数的功能。
举个例子:
let app = new Observer({name: 'liujianhuan',age: 25, company: 'Qihoo 360', address: 'Chaoyang, Beijing' }) app.$watch('age', function(age){ console.log(`我的年龄变了,现在是:${age}岁了`); }) app.data.basicInfo.age = 20;//输出:'我的年龄变了,现在已经是20岁了'
针对上述场景,我们需要实现$watch这个API,每当年龄发生改变的时候触发相应的回调函数。这个API的实现可以很有多种方式,在此我们采用事件的方式来实现,通俗的讲就是实现一个通用的事件模型,每次$watch一个属性相当于注册了一个监听事件,当属性发生改变的则触发对应的事件,这样做的优势是可以为同一个属性通过事件模型来注册多个回调函数。
下边是一个不完整的简易事件模型:
//实现一个事件
function Event(){ this.events = {}; } Event.prototype.on = function(attr, callback){ if(this.events[attr]){ this.events[attr].push(callback); }else{ this.events[attr] = [callback]; } } Event.prototype.off = function(attr){ for(let key in this.events){ if(this.events.hasOwnProperty(key) && key === attr){ delete this.events[key]; } } } Event.prototype.emit = function(attr, ...arg){ this.events[attr] && this.events[attr].forEach(function(item){ item(...arg); }) }
有了上述事件模型后,每次new一个Observer的实例时,就new一个Event实例出来用来管理Observer实例中的所有事件;然后通过$watch API来为Observer实例注册属性的监听事件,每次当属性改变的触发相应的事件队列。
function Observer (data) {//暂不考虑数组 this.data = data; this.makeObserver(data); this.eventsBus = new Event(); } Observer.prototype.setterAndGetter = function (key, val) { let _this = this; Object.defineProperty(this.data, key, { enumerable: true, configurable: true, get: function(){ console.log('你访问了' + key); return val; }, set: function(newVal){ console.log('你设置了' + key); console.log('新的' + key + '=' + newVal); //触发$watch函数 _this.eventsBus.emit(key, val, newVal); val = newVal; //如果newval是对象的话 if(typeof newVal === 'object'){ new Observer(val); } } }) } Observer.prototype.makeObserver = function (obj) { let val; for(let key in obj){ if(obj.hasOwnProperty(key)){ val = obj[key]; //深度遍历 if(typeof val === 'object'){ new Observer(val); } } this.setterAndGetter(key, val); } } Observer.prototype.$watch = function(attr, callback){ this.eventsBus.on(attr, callback); } let app = new Observer({ name: 'liujianhuan', age: 25, company: 'Qihoo 360', address: 'Chaoyang, Beijing' }) app.$watch('age', function(oldVal, newVal){ console.log(`我的年龄变了,原来是: ${oldVal}岁,现在是:${newVal}岁了`) }) app.$watch('age', function(oldVal, newVal){ console.log(`我的年龄真的变了诶,竟然年轻了${oldVal - newVal}岁`) }) app.data.basicInfo.age = 20;
测试结果如下:
测试结果显示上述代码触发了所注册的两个回调,但是上述代码也还是有着问题,比如目前只可注册监听对象的第一层的属性,对于对象的深层属性并不能有效监听,比如:
let app = new Observer({basicInfo: {name: 'liujianhuan', age: 25 }, company: 'Qihoo 360', address: 'Chaoyang, Beijing' }) app.$watch('age', function(age){ console.log(`我的年龄变了,现在是:${age}岁了`); }) app.data.basicInfo.age = 20;
这段代码中的回调并不会触发,这个问题留下来在后续中完善补充。
总结一下本文中针对“动态数据绑定”还未解决掉的问题:
- 当传入的参数为数组时,如何监听数组对象的变化
- 深层对象属性的事件回调监听,或者描述为:对象的深层属性值发生变化后如何向上传递到顶层
- 动态数据与视图的绑定,如何绑定,当数据变化后如何触发视图的自动刷新。
另外附上两个最近使用Vue实现的 Vue在SKU组合查询中应用 和 基于Vue的后台管理模板
---恢复内容结束---
转载于:https://www.cnblogs.com/changningios/p/6506563.html
动态数据绑定之监听对象变化相关推荐
- [vue] watch怎么深度监听对象变化
[vue] watch怎么深度监听对象变化 deep设置为true 就可以监听到对象的变化let vm=new Vue({el:"#first",data:{msg:{name:' ...
- watch监听对象里面值的变化_Vue总结——computed和watch的用法和区别
computed--计算属性 写法一: 写法二: computed默认有缓存 如果被依赖的属性没有发生变化,就不会重新计算. 什么是变化: 点击n+1按钮,控制台打出 n变了 点击obj.a=hi , ...
- Android 第十九课 大喇叭--广播机制----动态注册监听网络变化与静态注册实现开机启动
为了便于进行 系统级别的消息通知,Android引入了一套广播消息机制. 1.广播机制简介: 因为Android中的每个应用程序都可以对自己感兴趣的广播尽心注册,这样程序只会接收自己所关心的广播内容, ...
- Vue.js开发记录--用watch监听对象中属性的变化
监听对象中所有属性 存在对象obj,若想要监听其中所有的值的变化 watch: {obj: {handler (val) {// coding},deep: true}, } 监听对象中某个属性 如果 ...
- vue 监听表格里的数据变化_vue中监听数据变化 watch
今天做项目的时候,子组件中数据(原本固定的数据)需要父组件动态传入,如果一开始初始化用到的数据.但当时还没有获取到,初始化结束就不会更新数据了.只有监听这两个属性,再重新执行初始化. 1.watch是 ...
- Java实现监听文件变化的三种方法,推荐第三种
背景 在研究规则引擎时,如果规则以文件的形式存储,那么就需要监听指定的目录或文件来感知规则是否变化,进而进行加载.当然,在其他业务场景下,比如想实现配置文件的动态加载.日志文件的监听.FTP文件变动监 ...
- web前端面试高频考点——Vue原理(理解MVVM模型、深度/监听data变化、监听数组变化、深入了解虚拟DOM)
系列文章目录 内容 参考链接 Vue基本使用 Vue的基本使用(一文掌握Vue最基础的知识点) Vue通信和高级特性 Vue组件间的通信及高级特性(多种组件间的通信.自定义v-model.nextTi ...
- Angular.js中使用$watch监听模型变化
$watch简单使用 $watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你. $watch(watchExpression, listener, objectEqua ...
- 微信小程序,实现 watch 属性,监听数据变化
转自微信小程序,实现 watch 属性,监听数据变化 目标 在微信小程序实现 watch 属性,监听 data 中的属性,当被监听属性的值改变时,执行我们指定的方法. 思路 Vue 的 compu ...
最新文章
- SpringBoot (一) :入门篇 Hello World
- 华为公布车联网进展:年内将覆盖10万网联车
- 《Adobe Flash Professional CC经典教程》——1.3 使用“库”面板
- mysql开源内库_将内裤穿在外面的男人(mysql)
- [css] 有哪些方式可以对一个DOM设置它的CSS样式?
- C++和Rust_Kotlin、Rust两个充满了骚操作的编程语言,值得一玩
- centos7php自启动,centos7系统下nginx安装并配置开机自启动操作
- 【异常】INFO: TopologyManager: EndpointListener changed ...
- 一汽大众汽车宣布召回19.1万辆国产奥迪A6L
- 巧用gmail转发邮件
- 使用 Kotlin Script 自定义实现项目开发脚手架
- 系统性能评价的关键指标指标
- linux内核开发常用站点
- java 实现正弦曲线(模仿、练习Java swing)
- 士兵杀敌(coduck)
- 一篇文章搞懂 HDFS ACLs权限管理
- 如何快速学习一个开源框架
- 大数据常用十种开发语言
- Pikachu靶场之文件包含漏洞详解
- VS2010高速绘图Hight-Speed Charting -- 函数说明
热门文章
- 如果知道一个控件类型的对话框句柄是编辑框控件
- Android适配难题全面总结
- Python3 类和继承和组合
- salt '*' state.highstate 报错找不到文件,环境如下No Top file or master_tops data matches found....
- 团队-科学计算器-成员简介及分工
- 【Android】3.22 示例22--LBS云检索功能
- lintcode 滑动窗口的最大值(双端队列)
- linux 下 Nginx 0.8.40的安装
- 简单的OSPF多区配置
- 学习Python语言 基础语法:变量的基本使用