緩衝系統我以前在部落格寫過了,此後我對javascript的哲學發生很大的改變。以前是盡量避免對原型進行擴充的,但反過來一想,有什麼關係?除非是傻子或特別菜的人才會混用多個庫,能混用庫的人也只有高手才能避免命名衝突的尷尬。十大類庫中,前面幾個都是對原型進行瘋狂擴充的。像mootools,代碼不多,但能實在比jQuery多許多的功能。這就得益於原型擴充帶來的代碼量的減少。當然,我們還是要避免對Object進行擴充,這個真的是牽一髮動全身。
在我們開始時先列出一些要用到的工具(突然想起烹飪節目中的材料介紹……orz)
dom.mixin = function(result, source) { if (arguments.length === 1) { source = result; result = dom; } if (result && source ){ for(var key in source) source.hasOwnProperty(key) && (result[key] = source[key]); } if(arguments.length > 2 ){ var others = [].slice.call(arguments,2); for(var i=0,n=others.length;i<n;i++){ result = arguments.callee(result,others[i]); } } return result; }
dom.mixin = function(result, source) { if (arguments.length === 1) { source = result; result = dom; } if (result && source ){ for(var key in source) source.hasOwnProperty(key) && (result[key] = source[key]); } if(arguments.length > 2 ){ var others = [].slice.call(arguments,2); for(var i=0,n=others.length;i<n;i++){ result = arguments.callee(result,others[i]); } } return result; } var fn = "prototype", toString = Object[fn].toString; dom.mixin({ //類型識別 is : function (obj,type) { var undefined; return (type === "Null" && obj === null) || (type === "Undefined" && obj === undefined ) || toString.call(obj).slice(8,-1) === type; }, //分支對象構建 oneObject : function(arr,val){ var result = {},value = val !== undefined ? val :1; for(var i=0,n=arr.length;i<n;i++) result[arr[i]] = value; return result; }, deepCopy:function(result, source){ for(var key in source) { var copy = source[key]; if(result === copy) continue; if(dom.is(copy,"Object")){ result[key] = arguments.callee(result[key] || {}, copy); }else if(dom.is(copy,"Array")){ result[key] = arguments.callee(result[key] || [], copy); }else{ result[key] = copy; } } return result; }, //檢測是否為空白對象,如果第二參數為true,則只檢測區域屬性 //此方法對沒有hasOwnProperty屬性的對象無效 isEmptyObject: function(obj,local ) { for ( var key in obj ) { if (!!local){ if(obj.hasOwnProperty(key)) return false; }else{ return false; } } return true; } });
下面是緩衝系統:
var noData = dom.oneObject(["embed","object","applet"]), uuid = 1,windowdata = {},expando = "dom" + (Math.random()+"").slice(-8); dom.mixin({ cache: {}, expando:expando, //讀寫緩衝體 store: function( target, name, data ) { if ( target.nodeName && noData[target.nodeName.toLowerCase()] ) { return; } target = target == window ? windowData :target; //給目標對象設定一個自訂屬性,注意這屬性名稱是隨機的, var key , cache = dom.cache, thisCache; //如果是第一次訪問,目標對象沒有代表uuid的自定屬性,需要設定一個。 //注意這個屬性每次重新整理頁面都是不一樣的 //以uuid作為訪問緩衝體的鑰匙,如果沒有則設定一個,其值恒為一個對象 if(!(expando in target)){ key = target[ expando ] = uuid++; thisCache = cache[ key ] = {}; }else{ key = target[expando]; thisCache = cache[ key ]; } //通過Object一次把許多東西寫入 if ( dom.is(name,"Object") ) { thisCache = dom.deepCopy(thisCache, name); //如果name為字串並且data不為undefined,則每一次唯寫入一個索引值對 } else if ( data !== undefined ) { thisCache[ name ] = data; } return dom.is(name,"String") ? thisCache[ name ] : thisCache; }, //移除緩衝體 unstore: function( target, name ) { if ( target.nodeName && noData[target.nodeName.toLowerCase()] ) { return; } target = target == window ? windowData : target; if(!(expando in target)){ return } var key = target[ expando ], cache = dom.cache, thisCache = cache[ key ]; //如果傳入兩個參數, if ( name ) { if ( thisCache ) { //移除指定的條目 delete thisCache[ name ]; //如果緩衝體為空白,留著也沒用,刪掉 if ( dom.isEmptyObject(thisCache,true) ) { arguments.callee( target ); } } //如果只傳入一個參數,那麼連目標對象上的自訂屬性也一同移除 } else { //注意,IE8,FF等新銳瀏覽器的元素節點利用removeAttribute是無法把 //通過 "."這種圓點定址風格設定的屬性刪除掉的,必須使用delete操作符才行 try{ delete target[ expando ]; }catch(e){ //IE6、IE7則恰恰相反,使用delete操作符來刪除屬性會報錯,必須使用removeAttribute方法 target.removeAttribute( expando ); } // 緩衝體上的資料 delete cache[ key ]; } } });
這裡有一個細節就是刪除元素節點的自訂屬性,IE6IE7要特殊處理一下,一般而言直接通過 "."這種圓點定址風格或數組標記風格來設定的都叫屬性,而通過setAttribute設定的才是特性。而IE6IE7恰恰要用removeAttribute移除才行。這再次一次證明了其屬性特性不分的糟糕實現。
<br /><!doctype html><br /><html lang="en"><br /> <head><br /> <meta charset="utf-8" /><br /> <meta content="IE=8" http-equiv="X-UA-Compatible"/><br /> <title>緩衝系統 by 司徒正美</title><br /> <style type="text/css"><br /> #target{<br /> width:400px;<br /> height:100px;<br /> background:blue;<br /> }<br /> </style><br /> <script type="text/javascript"></p><p> Array.prototype.indexOf = function (el, index) {<br /> var n = this.length>>>0,<br /> i = index == null ? 0 : index < 0 ? Math.max(0, n + index) : index;<br /> for (; i < n; i++)<br /> if (i in this && this[i] === el) return i;<br /> return -1;<br /> };<br /> //http://msdn.microsoft.com/zh-cn/library/bb383786.aspx<br /> //移除 Array 對象中某個元素的第一個匹配項。<br /> Array.prototype.remove= function (item) {<br /> var index = this.indexOf(item);<br /> if (index !== -1) return this.removeAt(index);<br /> return null;<br /> };<br /> //移除 Array 對象中指定位置的元素。<br /> Array.prototype.removeAt= function (index) {<br /> return this.splice(index, 1)<br /> };</p><p> (function(){<br /> dom = {};<br /> dom.mixin = function(result, source) {<br /> if (arguments.length === 1) {<br /> source = result;<br /> result = dom;<br /> }<br /> if (result && source ){<br /> for(var key in source)<br /> source.hasOwnProperty(key) && (result[key] = source[key]);<br /> }<br /> if(arguments.length > 2 ){<br /> var others = [].slice.call(arguments,2);<br /> for(var i=0,n=others.length;i<n;i++){<br /> result = arguments.callee(result,others[i]);<br /> }<br /> }<br /> return result;<br /> }<br /> var fn = "prototype",<br /> toString = Object[fn].toString;<br /> dom.mixin({<br /> //類型識別<br /> is : function (obj,type) {<br /> var undefined;<br /> return (type === "Null" && obj === null) ||<br /> (type === "Undefined" && obj === undefined ) ||<br /> toString.call(obj).slice(8,-1) === type;<br /> },<br /> //分支對象構建<br /> oneObject : function(arr,val){<br /> var result = {},value = val !== undefined ? val :1;<br /> for(var i=0,n=arr.length;i<n;i++)<br /> result[arr[i]] = value;<br /> return result;<br /> },<br /> deepCopy:function(result, source){<br /> for(var key in source) {<br /> var copy = source[key];<br /> if(result === copy) continue;<br /> if(dom.is(copy,"Object")){<br /> result[key] = arguments.callee(result[key] || {}, copy);<br /> }else if(dom.is(copy,"Array")){<br /> result[key] = arguments.callee(result[key] || [], copy);<br /> }else{<br /> result[key] = copy;<br /> }<br /> }<br /> return result;<br /> },<br /> //檢測是否為空白對象,如果第二參數為true,則只檢測區域屬性<br /> //此方法對沒有hasOwnProperty屬性的對象無效<br /> isEmptyObject: function(obj,local ) {<br /> for ( var key in obj ) {<br /> if (!!local){<br /> if(obj.hasOwnProperty(key))<br /> return false;<br /> }else{<br /> return false;<br /> }<br /> }<br /> return true;<br /> }<br /> });</p><p> var noData = dom.oneObject(["embed","object","applet"]),<br /> uuid = 1,windowdata = {},expando = "dom" + (Math.random()+"").slice(-8);<br /> dom.mixin({<br /> cache: {},<br /> expando:expando,<br /> //讀寫緩衝體<br /> store: function( target, name, data ) {<br /> if ( target.nodeName && noData[target.nodeName.toLowerCase()] ) {<br /> return;<br /> }<br /> target = target == window ? windowData :target;<br /> //給目標對象設定一個自訂屬性,注意這屬性名稱是隨機的,<br /> var key , cache = dom.cache, thisCache;<br /> //如果是第一次訪問,目標對象沒有代表uuid的自定屬性,需要設定一個。<br /> //注意這個屬性每次重新整理頁面都是不一樣的<br /> //以uuid作為訪問緩衝體的鑰匙,如果沒有則設定一個,其值恒為一個對象<br /> if(!(expando in target)){<br /> key = target[ expando ] = uuid++;<br /> thisCache = cache[ key ] = {};<br /> }else{<br /> key = target[expando];<br /> thisCache = cache[ key ];<br /> }<br /> //通過Object一次把許多東西寫入<br /> if ( dom.is(name,"Object") ) {<br /> thisCache = dom.deepCopy(thisCache, name);<br /> //如果name為字串並且data不為undefined,則每一次唯寫入一個索引值對<br /> } else if ( data !== undefined ) {<br /> thisCache[ name ] = data;<br /> }<br /> return dom.is(name,"String") ? thisCache[ name ] : thisCache;<br /> },<br /> //移除緩衝體<br /> unstore: function( target, name ) {<br /> if ( target.nodeName && noData[target.nodeName.toLowerCase()] ) {<br /> return;<br /> }<br /> target = target == window ? windowData : target;<br /> if(!(expando in target)){<br /> return<br /> }<br /> var key = target[ expando ], cache = dom.cache, thisCache = cache[ key ];<br /> //如果傳入兩個參數,<br /> if ( name ) {<br /> if ( thisCache ) {<br /> //移除指定的條目<br /> delete thisCache[ name ];<br /> //如果緩衝體為空白,留著也沒用,刪掉<br /> if ( dom.isEmptyObject(thisCache,true) ) {<br /> arguments.callee( target );<br /> }<br /> }<br /> //如果只傳入一個參數,那麼連目標對象上的自訂屬性也一同移除<br /> } else {<br /> //注意,IE8,FF等新銳瀏覽器的元素節點利用removeAttribute是無法把<br /> //通過 "."這種圓點定址風格設定的屬性刪除掉的,必須使用delete操作符才行<br /> try{<br /> delete target[ expando ];<br /> }catch(e){<br /> //IE6、IE7則恰恰相反,使用delete操作符來刪除屬性會報錯,必須使用removeAttribute方法<br /> target.removeAttribute( expando );<br /> }<br /> // 緩衝體上的資料<br /> delete cache[ key ];<br /> }<br /> }<br /> });<br /> })();</p><p> var exec1 = function(){<br /> dom.store(document.body, 'foo', 52);<br /> dom.store(document.body, 'bar', 'test');<br /> alert( dom.store(document.body, 'foo'))<br /> alert( dom.store(document.body, 'bar'));<br /> var memory = dom.store(document.body);<br /> for(var i in memory){<br /> alert(i + " "+memory[i]);<br /> }<br /> }<br /> var exec2 = function(){<br /> alert("0、 "+ document.body[dom.expando]);<br /> dom.unstore(document.body, 'foo');<br /> alert("1、 "+ dom.store(document.body, 'foo'))<br /> alert("2、 "+dom.store(document.body));<br /> dom.unstore(document.body,'bar');<br /> alert("3、 "+ dom.store(document.body, 'bar'))<br /> alert("4、 "+ dom.store(document.body));<br /> alert("5、 "+ document.body[dom.expando]);<br /> }<br /> var exec3 = function(){<br /> dom.store(document.body, 'aa', "司徒正美");<br /> alert(dom.store(document.body, 'aa'));<br /> dom.store(document.body, {aa:"ruby",bb:"louvre"});<br /> alert(dom.store(document.body, 'aa'));<br /> alert(dom.store(document.body, 'bb'));<br /> }<br /> </script><br /> </head><br /> <body><br /> <pre><br /> dom.store(document.body, 'foo', 52);<br /> dom.store(document.body, 'bar', 'test');<br /> alert( dom.store(document.body, 'foo'))<br /> alert( dom.store(document.body, 'bar'));<br /> var memory = dom.store(document.body);<br /> for(var i in memory){<br /> alert(i + " "+memory[i]);<br /> }<br /> </pre><br /> <button type="button" onclick="exec1()">點我實現上面的操作</button><br /> <pre><br /> alert("0、 "+ document.body[dom.expando]);<br /> dom.unstore(document.body, 'foo');<br /> alert("1、 "+ dom.store(document.body, 'foo'))<br /> alert("2、 "+dom.store(document.body));<br /> dom.unstore(document.body,'bar');<br /> alert("3、 "+ dom.store(document.body, 'bar'));//由於自訂屬性與緩衝體已刪光了,<br /> alert("4、 "+ dom.store(document.body)); //再次用stor訪問時只好重設它們<br /> alert("5、 "+ document.body[dom.expando]);<br /> </pre><br /> <button type="button" onclick="exec2()">點我實現上面的操作</button><br /> <pre><br /> dom.store(document.body, 'aa', "司徒正美");<br /> alert(dom.store(document.body, 'aa'));<br /> dom.store(document.body, {aa:"ruby",bb:"louvre"});<br /> alert(dom.store(document.body, 'aa'));<br /> alert(dom.store(document.body, 'bb'));<br /> </pre><br /> <button type="button" onclick="exec3()">點我實現上面的操作</button><br /> </body><br /></html><br />
運行代碼
不過,還有個問題,如果我們要傳入一組資料呢?然後每次添加都是在這個數組中操作,原來的系統就不靈光了。以前我的緩衝系統是通過判定屬性名稱來確定是儲存單個資料還是列表還是映射。但翻譯ECMA時,有句話是這樣說的,我們不應該在原函數的參數上做文章來實現新的功能,而是建立一個函數來實現它。這樣做的好處是易維護。jQuery就是這樣做的,我們跟著模仿就是。模仿多了自然會創新。
相關連結:
javascript 跨瀏覽器的事件系統