瀏覽器自帶的觀察者實在太多了。經典的不用說,就是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上實現了。
//http://stackoverflow.com/questions/1700870/how-do-i-do-outerhtml-in-firefox
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 + "
";
});
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,只能用於元素節點
//bug的詳解見這里http://www.cnblogs.com/_franky/archive/2011/04/27/2030766.html
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提供了一系列高級的變動事件:
DOMAttrModifiedDOMAttributeNameChangedDOMCharacterDataModifiedDOMElementNameChangedDOMNodeInsertedDOMNodeInsertedIntoDocumentDOMNodeRemovedDOMNodeRemovedFromDocumentDOMSubtreeModified
這下好了,無論是你是元素做增刪改操作,還是元素的孩子們做增刪改操作,還是對它的innerHTML或是屬性進行增刪改操作,它都提供監聽。早期jQuery的Sizzle就是利用過DOMAttrModified清查調緩存的。我們可以在這里查到它們的用法。但一個問題是,瀏覽器商對此不怎么熱衷,太復雜了,有太多了,太麻煩了,而且這類事件也不好用JS檢測是否支持。
在ecma262v6中, 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非常重要,負責對視圖的變化進行監聽,再配合已存的事件系統,形成一個密不透風的網,監聽與揣測着用戶的一舉一動,堪比國安局啊,就像每家蘭州拉面旁邊必有個沙縣小吃!
