用麻雀虽小五脏俱全来描述Watch.js比较合适。“观察者”模式是我们在开发的时候经常需要用到的。使用Watch.js那么我们就可以实现在“每当对象属性改变的时候,执行你的函数”。虽然有很多其他的库可以实现相同的功能,但是Watch.js却可以不改变你平时书写代码的方式,并且实现属性改变的监听功能。
让我们来看看它的API:
//使用任意一种方式定义一个对象
var ex1 = {
attr1: "initial value of attr1",
attr2: "initial value of attr2"
};
//针对其中一个属性“attr1”设置“观察者”
watch(ex1, "attr1", function(){
alert("attr1 changed!");
});
//当“attr1”修改的时候“观察者”函数会被调用
ex1.attr1 = "other value";`</pre>
[try demo](http://jsfiddle.net/NbJuh/17/)
Watch.js还提供了其他有用的api:监控多个属性、监控整个对象或移除整一个watch。你可能会想到循环调用,例如:
//使用任意一种方式定义一个对象
var ex1 = {
attr1: "inicial value of attr1",
attr2: "initial value of attr2"
};
//定义“attr1”属性的监听函数
watch(ex1, "attr1", function(){
//在此域中阻止监听器的调用
WatchJS.noMore = true;
ex1.attr2 = ex1.attr1 + " + 1";
});
//给另一个属性定义“监听函数”
watch(ex1, "attr2", function(){
alert("attr2 changes");
});
//attr1修改的时候,会修改attr2但不会触发attr2的“监听函数”
ex1.attr1 = "other value to 1";
WatchJS.noMore = true
可以确保在此次监听函数的调用中,对属性的修改不会触发新的监听函数。接下来看看它是如何实现它们的!
Object.defineProperty
是ECMAScript 5标准提供的方法,它允许你在一个对象上定义一个新属性或是修改原有属性的描述符。这个方法接收一个属性描述符,并用它来初始化(或更新)一个属性。例如:
(function() {
var obj = {},
value = 1;
Object.defineProperty( obj, "value", {
// value: true,
// writable: false,
enumerable: true,
configurable: true,
get: function() {
return value;
},
set: function(v) {
value = v);
}
});
})();
defineProperty
的第三个参数是描述符,value
设置属性值,writable
表示属性是否可写(不会报错,只是写操作不生效),enumerable
表示属性是否可以通过for in
迭代获取,configurable
表示属性该属性的描述符无法再被定义,并且属性也无法被delete
。get
和set
分别定义属性的access方法,注意不可同时定义属性的accessor和value及writable参数。具体的方法描述参考MDN上的文档
Watch.js利用了属性的accessor方法实现了对属性变化的监听,代码如下:
defineWatcher = function (obj, prop, watcher) {
var val = obj[prop];
// 监听数组的操作函数,用于数组变化的监听
watchFunctions(obj, prop);
// 初始化watchers对象,用以存储监听者
if (!obj.watchers) {
defineProp(obj, "watchers", {});
}
// 初始化针对此属性的监听者数组
if (!obj.watchers[prop]) {
obj.watchers[prop] = [];
}
// 添加监听者
obj.watchers[prop].push(watcher);
var getter = function () {
return val;
};
var setter = function (newval) {
var oldval = val;
val = newval;
// 监听对象上所有属性
if(obj[prop]){
watchAll(obj[prop], watcher);
}
// 监听数组的操作函数
watchFunctions(obj, prop);
// 将对象转换成字符串之后判断是否有改变
if (JSON.stringify(oldval) !== JSON.stringify(newval)) {
if (!WatchJS.noMore){
callWatchers(obj, prop, newval, oldval);
WatchJS.noMore = false;
}
}
};
// 定义accessor
defineGetAndSet(obj, prop, getter, setter);
};
defineWatcher
函数是定义监听者的主要函数,它做了如下几件事情:
defineGetAndSet
是利用了defineProperty
来设置监听函数,如果不支持则会尝试如下方法:
Object.prototype.__defineGetter__.call(obj, propName, getter);
Object.prototype.__defineSetter__.call(obj, propName, setter);
有些稍微旧版本的先进浏览器可以使用这个方法来设置accessor。如果浏览器还是不支持那么会fallback到设置定时器监控。如下:
var subjects = [];
defineWatcher = function(obj, prop, watcher){
// 存储当前状态和监听者
subjects.push({
obj: obj,
prop: prop,
serialized: JSON.stringify(obj[prop]), // 设置当前属性值的序列化值
watcher: watcher
});
};
unwatchOne = function (obj, prop, watcher) {
for (var i in subjects) {
var subj = subjects[i];
if (subj.obj == obj && subj.prop == prop && subj.watcher == watcher) {
subjects.splice(i, 1);
}
}
};
callWatchers = function (obj, prop) {
for (var i in subjects) {
var subj = subjects[i];
if (subj.obj == obj && subj.prop == prop) {
subj.watcher.call(obj, prop);
}
}
};
var loop = function(){
for(var i in subjects){
var subj = subjects[i];
// 新的获取序列化值
var newSer = JSON.stringify(subj.obj[subj.prop]);
// 与旧的序列化值判断
if(newSer != subj.serialized){
// 不同则调用监听函数
subj.watcher.call(subj.obj, subj.prop, subj.obj[subj.prop], JSON.parse(subj.serialized));
// 设置新的值
subj.serialized = newSer;
}
}
};
setInterval(loop, 50);
这个降级处理是存在bug的,它无法正确处理属性设置之间的间隔。原因在于javascript的线程模型,以及setInterval
不是立即能知道更新,有50ms的间隔时间。样例如下:
var obj = {
a: 1
};
function cb() {
console.log(this.a);
}
watch(obj, 'a', cb);
console.log('set first');
obj.a = 2;
console.log('set second');
obj.a = 3;
上面的代码在支持defineProperty
的浏览器中执行结果如下:
set first
2
set second
3
在不支持的浏览器中执行结果如下:
set first
set second
3
可见setInterval
的降级处理是异步的,而defineProperty
是同步的。这在一些情况下会导致严重的bug,而且难以调试。所以在真正使用的环境中需要注意!
抛开watch.js的bug不谈,它还是有很多可圈可点的地方。使用下来觉得watch.js的优点如下:
缺点如下:
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有