浏览器自带的观察者实在太多了。经典的不用说,就是onclick, attachEvent, addEventListner,可惜它们只是监听用户的行为。不过这当中有个特例是propertychange,当元素的属性,不管是自定义还是原生,只要发生改变,就会触发回调。我们还可以通过它的事件对象的propertyName知道那个元素发生改变。标准浏览器有个弱化版oninput,只能检测value值!
FF则有个__noSuchMethod__,只通在用户调用方法时纠错用。后来,又搞出个逆天的Object.prototype.watch,由于元素节点在FF中也是Object的实例,其威力可想而已。但也有美中不足,我们不知道究竟是哪一个属性发生变化。
但这一状况随着浏览器对setter,getter的强力介入得到改善。早在Firefox 2.0+, Safari 3.0+, Chrome 1.0+ 与 Opera 9.5+,他们就口径一致地添加以下方法,支持这种新语法:
var
lost = {
loc :
"Island"
,
get location () {
//这里可以添加回调
return
this
.loc;
},
set location(val) {
//这里也可以搞小动作
this
.loc = val;
}
};
lost.location =
"Another island"
;
|
但这种新语法在IE8以下是会报致命错误,连try catch也挡不住,因此对这种兼容性极差的东西,程序员们不埋单,于是浏览器商又推销另一种新产品:
Object.defineProperty(document.body,
"description"
, {
get :
function
() {
return
this
.desc;
},
set :
function
(val) {
this
.desc = val;
}
});
document.body.description =
"Content container"
;
|
著名的例子是在FF模拟outerHTML,不过这东西最终在FF11上实现了。
if
(
typeof
(HTMLElement) !=
"undefined"
&& !window.opera)
{
HTMLElement.prototype._____defineGetter_____(
"outerHTML"
,
function
()
{
var
a =
this
.attributes, str =
"<"
+
this
.tagName, i = 0;
for
(; i < a.length; i++)
if
(a[i].specified)
str +=
" "
+ a[i].name +
'="'
+ a[i].value +
'"'
;
if
(!
this
.canHaveChildren)
return
str +
" />"
;
return
str +
">"
+
this
.innerHTML +
"<!--"
+
this
.tagName +
"-->"
;
});
HTMLElement.prototype._____defineSetter_____(
"outerHTML"
,
function
(s)
{
var
r =
this
.ownerDocument.createRange();
r.setStartBefore(
this
);
var
df = r.createContextualFragment(s);
this
.parentNode.replaceChild(df,
this
);
return
s;
});
HTMLElement.prototype._____defineGetter_____(
"canHaveChildren"
,
function
()
{
return
!/^(area|base|basefont|col|frame|hr|img|br|input|isindex|link|meta|param)$/.test(
this
.tagName.toLowerCase());
});
}
|
IE自有自己一套算盘,它使用Object.DefineProperty数据描述符实现 setter与getter。不过这东西在IE8有BUG,只能用于元素节点
Object.defineProperty(document.body,
"description"
, {
get :
function
() {
return
this
.desc;
},
set :
function
(val) {
this
.desc = val;
}
});
document.body.description =
"Content container"
;
// document.body.description will now return "Content container"
|
但setter,getter就是setter,getter,我们不应该在这里掺和,于是W3C提供了一系列高级的变动事件:
DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
这下好了,无论是你是元素做增删改操作,还是元素的孩子们做增删改操作,还是对它的innerHTML或是属性进行增删改操作,它都提供监听。早期jQuery的Sizzle就是利用过DOMAttrModified清查调缓存的。我们可以在这里查到它们的用法。但一个问题是,浏览器商对此不怎么热衷,太复杂了,有太多了,太麻烦了,而且这类事件也不好用JS检测是否支持。
在ecma262v6k ,FF开始推销它的一个好东西,Proxy!它相当于ecma262v5的数据描述符的强化版,但暂时没有其他浏览器商埋单。搞不好像IE8实现setter,getter那样,换个名字上场。
不过像propertychange这样的东西太重要了,老麻烦setInterval太不意思了。现在onhashchange, oninput都出来了,总有人干这事。时代在招唤!MutationObserver终于应运而生!而且MutationObserver是出乎意料的强大,把上面一系列Mutation Event的活都干了,而且出身好了,已列入W3C草案,MDC的文档,FF14说好会支持它,而chrome18已实现了。
DOM MutationObserver – reacting to DOM changes without killing browser performance.给出一个例子实现即时编辑:
<!doctype html>
<html>
<head>
<title>mass Framework</title>
<meta http-equiv=
"content-type"
content=
"text/html; charset=UTF-8"
/>
</head>
<body>
<ol contenteditable oninput=
""
>
<li>Press enter</li>
</ol>
<script>
window.onload =
function
(){
var
MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var
list = document.querySelector(
'input'
);
var
observer =
new
MutationObserver(
function
(mutations) {
mutations.forEach(
function
(mutation) {
if
(mutation.type ===
'childList'
) {
var
list_values = [].slice.call(list.children)
.map(
function
(node) {
return
node.innerHTML; })
.filter(
function
(s) {
if
(s ===
'<br>'
) {
return
false
;
}
else
{
return
true
;
}
});
console.log(list_values);
}
});
});
observer.observe(list, {
attributes:
true
,
childList:
true
,
characterData:
true
,
});
}
</script>
</body>
</html>
|
如果翻看W3C, 我们可以找到更多用法:
<!doctype html>
<html>
<head>
<title>mass Framework</title>
<meta http-equiv=
"content-type"
content=
"text/html; charset=UTF-8"
/>
</head>
<body>
<input value=
"aaa"
>
<script>
window.onload =
function
(){
var
MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var
list = document.querySelector(
'input'
);
var
observer =
new
MutationObserver(
function
(mutations) {
console.log(mutations)
mutations.forEach(
function
(record) {
if
(record.attributeName ==
"value"
){
console.log(record.target)
console.log(record.oldValue)
}
});
});
observer.observe(list, {
attributes:
true
,
childList:
true
,
characterData:
true
,
attributeOldValue :
true
,
attributeFilter:[
"value"
]
//只监听value属性,提高性能
});
list.setAttribute(
"value"
,
"bbb"
)
list.setAttribute(
"value"
,
"ccc"
)
}
</script>
</body>
</html>
|
如此一来我们就可以轻松实现propertychange的功能,也不用趟setter, getter的浑水了。现在它对前端实现MVC非常重要,负责对视图的变化进行监听,再配合已存的事件系统,形成一个密不透风的网,监听与揣测着用户的一举一动,堪比国安局啊,就像每家兰州拉面旁边必有个沙县小吃!