我们先来看一个简单的实现思路。
// 定义一个变化通知的回调
var callback = function(newVal, oldVal) {
alert(newVal + '---' + oldVal)
}
// 定义一个普通对象作为数据模型
var data = {
a: 10,
level1: {
b: 'str',
c: [1,2,3],
level2: {
d: 30
}
}
}
// 实例化一个检测对象,去检测数据,并在数据发生改变的时候做出反应
var j = new Jsonob(data, callback)
上面代码中,我们定义了一个 callback 回调函数,以及一个保存着普通 json 对象的变量 data,最后实例化了一个监测对象,对 data 进行变化监测,当变化发生的时候,执行给定的回调进行必要的变化通知,这样,我们通过一些手段就可以达到数据绑定的效果。
Object.defineProperty
ES5 描述了属性的特征,提出对象的每个属性都有特定的描述符,你也可以理解为那是属性的属性。。。
ES5 把属性分成两种,一种是数据属性,一种是访问器属性,我们可以使用 Object.defineProperty() 去定义一个数据属性或访问器属性。如下代码:
var obj = {}
obj.name = 'yyf'
上面的代码我们定义了一个对象,并给这个对象添加了一个属性 name,值为 ‘yyf’,我们也可以使用 Object.defineProperty() 来给对象定义属性,上面的代码等价于:
var obj = {}
Object.defineProperty(obj, 'name', {
value: 'yyf', //属性的值
writable: true, //是否可写
enumerable: true, //是否能够通过for in枚举
configurable: true //是否可使用delete删除
})
这样我们就使用 Object.defineProperty 给对象定义了一个属性,这样的属性就是数据属性,我们也可以定义访问器属性:
var obj = {}
Object.defineProperty(obj, 'age', {
get: function() {
return 20
},
set: function(newVal) {
this.age += 20
}
})
访问器属性允许你定义一对 getter/setter ,当你读取属性值的时候底层会调用 get 方法,当你去设置属性值的时候,底层会调用 set 方法。
知道了这个就好办了,我们再回到最初的问题上面,如何检测一个普通对象的变化,我们可以这样做:
遍历对象的属性,把对象的属性都使用 Object.defineProperty 转为 getter/setter ,这样,当我们修改一些值的时候,就会调用 set 方法,然后我们在 set 方法里面,回调通知,不就可以了吗,来看下面的额代码:
// index.js
const OP = Object.prototype
export class Jsonob {
constructor(obj, callback) {
if(OP.toString.call(obj) !== '[object Object]') {
console.error('This parameter must be an object:' + obj)
}
this.$callback = callback
this.observe(obj)
}
observe(obj) {
Object,keys(obj).forEach(function(key,index,keyArray) {
var val = obj[key]
Object.defineProperty(obj, key, {
get: function(){return val},
set: (function(newVal){
this.$callback(newVal)
}).bind(this)
})
if(OP.toString.call(obj[key]) === '[object Object]') {
this.observe(obj[key])
}
}, this)
}
}
上面代码采用 ES6 编写,index.js 文件中导出了一个 Jsonob 类,constructor 构造函数中,我们保证了传入的对象是一个 { } 或 new Object() 生成的对象,接着缓存了回调函数,最后调用了原型下的 observe 方法。
observe() 方法是真正实现监测属性的方法,我们使用 Object.kes(obj).forEach 循环 obj 所有可枚举的属性,使用 Object.defineProperty 将属性转换为访问器属性,然后判断属性的值是否是一个对象,如果是对象的话再进行递归调用,这样一来,我们就能保证一个复杂的普通 json 对象中的属性以及值为对象的属性都转换成访问器属性。
最后,在 Object.defineProperty 的 set 方法中,我们调用了指定的回调,并将新值作为参数进行传递。