2012-03-16 70 views
0

我想寫一些純JavaScript,以便更好地理解它(我知道在這框架,比如jQuery的「真正實踐」是更建議和適用的,但這不是真正關於如何使用框架,更多關於純JavaScript如何工作和最佳實踐)。如何編寫圍繞DOM存儲數據(對象,自定義屬性)節點

反正我寫了一些簡單的javascript代碼。我想創建一組按鈕,每次只有一個狀態從set {on,off}開始,每個狀態都會映射到相應的函數,在進入該狀態時被觸發。主組中的每組按鈕一次只能包含一個處於打開狀態的按鈕。這個概念與單選按鈕的概念相似。 爲什麼不使用單選按鈕呢?在語義上,它只是假設是某些控件元素的按鈕,但是我想我可以擁有任何一種方式,但問題並不在於此。

問題是,爲了解決這個問題,我在我的Javascript中通過id向特定的button元素添加了很多自定義屬性。我正在做一些研究,發現這個questionquestion,關於在DOM節點(對象)上使用自定義屬性。他們似乎主張反對這種做法,甚至可以說這樣做可能導致潛在的內存泄漏,這取決於瀏覽器的實現。

然而,對於每個按鈕創建我需要保持大量的屬性的跟蹤,如果我擴大這個劇本我可能會更增加。那麼將它們存儲在DOM節點上的最好方法是什麼,但是仍然保持跟蹤所有內容,並且能夠在附加函數中使用等。

這是不容易明顯,我如何做到這一點而不在最小存儲井名隔開對象到DOM節點button元素的引用。

我可以看到,從這個question jQuery有一些方法可以做到這一點,但我想知道這只是純粹的JavaScript如何完成。

下面是完整的示例代碼,我有工作:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Button Test Script</title> 

<script language="javascript" type="text/javascript"> 

window.button_groups = {}; 

function isset(type) { 
    return !(type==='undefined'); 
} 

function debug(txt) { 
    if(!isset(typeof console)) { 
     alert(txt); 
    } else { 
     console.log(txt); 
    } 
} 

function img(src) { 
    var t = new Image(); 
    t.src = src; 
    return t;  
} 

function turnGroupOff(group) { 
    if(isset(typeof window.button_groups[group])) {    
     for(var i = 0; i < window.button_groups[group].length; i++) {   
      if(window.button_groups[group][i].toggle == 1) 
       window.button_groups[group][i].click(); 
     }    
    } 
} 
/** 
* buttonId = id attribute of <button> 
* offImg = src of img for off state of button 
* onImg = src of img for on state of button 
* on = function to be fired when button enters on state 
* off = function to be fired when button enters off state 
*/ 
function newButton(buttonId, offImg, onImg, group, on, off) { 

    var b = document.getElementById(buttonId); 
    b.offImg = img(offImg); 
    b.onImg = img(onImg); 
    b.on = on; 
    b.off = off; 
    b.img = document.createElement('img'); 
    b.appendChild(b.img); 
    b.img.src = b.offImg.src; 
    b.group = group; 

    b.toggle = 0; 

    b.onclick = function() { 
     switch(this.toggle) { 
     case 0:            
      turnGroupOff(this.group); 
      this.on(); 
      this.toggle = 1; 
      this.img.src = this.onImg.src; 
      break; 
     case 1: 
      this.off(); 
      this.toggle = 0; 
      this.img.src = this.offImg.src; 
      break; 
     }  
    } 

    if(!isset(typeof window.button_groups[group])) 
     window.button_groups[group] = []; 
    window.button_groups[group].push(b);      

} 


function init() { 

    var on = function() { debug(this.id + " turned on") }; 

    newButton('button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1', 
     on, 
     function() { debug(this.id + " turned off"); } 
     ); 
    newButton('button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1', 
     on, 
     function() { debug(this.id + " turned off (diff then usual turn off)"); } 
     ); 

    newButton('button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2', 
     on, 
     function() { debug(this.id + " turned off (diff then usual turn off2)"); } 
     ); 
    newButton('button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2', 
     on, 
     function() { debug(this.id + " turned off (diff then usual turn off3)"); } 
     ); 

} 

window.onload = init; 
</script> 

</head> 

<body> 


<button id="button1" type="button"></button> 
<button id="button2" type="button"></button> 
<br/> 
<button id="button3" type="button"></button> 
<button id="button4" type="button"></button> 

</body> 
</html> 

UPDATE

jQuery的事情是,我的目的有點大材小用。我不需要擴展任意元素。我對jQuery的具體做法有個很好的想法(隨機命名的屬性存儲緩存索引整數)。

我事先知道哪些主機元素需要擴展,以及如何;我也可以/想在HTML端設置一個id屬性。

所以,通過了jQuery設置的啓發,我決定也創建一個全局緩存變量,除了我將使用DOM節點的id屬性爲我的緩存鍵。由於它應該是一個唯一的標識符(根據定義),並且我沒有計劃動態改變ID的有史以來,這應該是一個簡單的任務。它將我的Javascript對象從DOM對象中徹底分離出來,但它確實使我的代碼看起來更加醜陋,並且很難通過對data的多次調用來閱讀。我提出以下修改:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Button Test Script</title> 
<script language="javascript" type="text/javascript"> 

window.button_groups = {}; 

function isset(type) { // For browsers that throw errors for !object syntax 
    return !(type==='undefined'); 
} 

var c = { // For browsers without console support 
    log: function(o) { 
     if(isset(typeof console)) { 
      console.log(o); 
     } else { 
      alert(o); 
     } 
    }, 
    dir: function(o) { 
     if(isset(typeof console)) { 
      console.dir(o); 
     } 
    } 
}; 

function img(src) { // To avoid repeats of setting new Image src 
    var t = new Image(); 
    t.src = src; 
    return t;  
} 

var cache = {}; 
function data(elemId, key, data) { // retrieve/set data tied to element id 

    if(isset(typeof data)) {// setting data   
     if(!isset(typeof cache[elemId])) 
      cache[elemId] = {}; 
     cache[elemId][key] = data; 

    } else { // retreiving data 
     return cache[elemId][key];  
    } 

} 

var button_groups = {}; // set of groups of buttons 

function turnGroupOff(group) { // turn off all buttons within a group 
    if(isset(typeof window.button_groups[group])) {    
     for(var i = 0; i < window.button_groups[group].length; i++) {   
      if(data(window.button_groups[group][i].id, 'toggle') == 1) 
       window.button_groups[group][i].click(); 
     }    
    } 
} 


/** 
* buttonId = id attribute of <button> 
* offImg = src of img for off state of button 
* onImg = src of img for on state of button 
* on = function to be fired when button enters on state 
* off = function to be fired when button enters off state 
*/ 
function newButton(buttonId, offImg, onImg, group, on, off) { 

    var b = document.getElementById(buttonId); 
    data(b.id, 'offImg', img(offImg)); 
    data(b.id, 'onImg', img(onImg)); 
    data(b.id, 'on', on); 
    data(b.id, 'off', off); 
    var btnImg = document.createElement('img'); 
    btnImg.src = data(b.id, 'offImg').src; 
    data(b.id, 'img', btnImg ); 
    b.appendChild(btnImg); 
    data(b.id, 'group', group); 
    data(b.id, 'toggle', 0); 

    var click = function() { 
     switch(data(this.id,'toggle')) { 
     case 0:            
      turnGroupOff(data(this.id,'group')); 
      (data(this.id,'on'))(); 
      data(this.id,'toggle',1); 
      data(this.id,'img').src = data(this.id,'onImg').src; 
      break; 
     case 1: 
      (data(this.id,'off'))(); 
      data(this.id,'toggle',0); 
      data(this.id,'img').src = data(this.id,'offImg').src; 
      break; 
     } 

    } 

    b.onclick = click; 

    if(!isset(typeof window.button_groups[group])) 
     window.button_groups[group] = []; 
    window.button_groups[group].push(b);      
} 


function init() { 

    var on = function() { c.log(this.id + " turned on") }; 

    newButton('button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1', 
     on, 
     function() { c.log(this.id + " turned off"); } 
     ); 
    newButton('button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1', 
     on, 
     function() { c.log(this.id + " turned off (diff then usual turn off)"); } 
     ); 

    newButton('button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2', 
     on, 
     function() { c.log(this.id + " turned off (diff then usual turn off2)"); } 
     ); 
    newButton('button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2', 
     on, 
     function() { c.log(this.id + " turned off (diff then usual turn off3)"); } 
     ); 

} 


window.onload = init; 


</script>  
</head> 

<body> 


<button id="button1" type="button"></button> 
<button id="button2" type="button"></button> 
<br/> 
<button id="button3" type="button"></button> 
<button id="button4" type="button"></button> 


</body> 
</html> 

UPDATE 2

我發現,通過使用閉合的功率I真正只需要存儲一個「特殊」屬性,即該組的按鈕曾經屬於。

我改變了newButton功能;下面,通過關閉,消除了需要存儲很多其他的事情我是:

function newButton(buttonId, offImg, onImg, group, on, off) { 

    var b = document.getElementById(buttonId); 
    offImg = img(offImg); 
    onImg = img(onImg); 
    var btnImg = document.createElement('img'); 
    btnImg.src = offImg.src; 
    b.appendChild(btnImg); 
    data(b.id, 'group', group); 
    var toggle = 0; 

    var click = function(event) { 
     switch(toggle) { 
     case 0:            
      turnGroupOff(data(this.id,'group')); 
      if(on(event)) { 
       toggle = 1; 
       btnImg.src = onImg.src; 
      } 
      break; 
     case 1: 
      if(off(event)) { 
       toggle = 0; 
       btnImg.src = offImg.src; 
      } 
      break; 
     } 

    } 

    b.onclick = click; 

    if(!isset(typeof window.button_groups[group])) 
     window.button_groups[group] = []; 
    window.button_groups[group].push(b);  

    b = null; 
} 
+0

jQuery在JavaScript中是_written_。看看[他們是怎麼做的](https://github.com/jquery/jquery/blob/master/src/data.js),特別是看看'data()'函數。 – voithos 2012-03-16 16:41:21

回答

1

您可以擴展對象(這對主機對象不利),也可以像jQuery那樣包裝對象,使用包裝對象標識哈希表中的關聯數據。實質上,您可以散列DOM節點並在關聯數據的哈希表中進行查找。當然,你仍然需要擴展宿主對象,但是你只添加一個你知道在瀏覽器中添加相當安全的屬性,而不是一組任意屬性。如果您檢查包含關聯數據的元素,則可能會看到類似於element.jQuery171023696433915756643的內容,其中包含該元素的內部存儲索引。如果你感興趣的話,我會推薦閱讀jQuery源代碼,特別是數據()函數

data: function(elem, name, data, pvt /* Internal Use Only */) { 
     if (!jQuery.acceptData(elem)) { 
      return; 
     } 

     var privateCache, thisCache, ret, 
      internalKey = jQuery.expando, 
      getByName = typeof name === "string", 

      // We have to handle DOM nodes and JS objects differently because IE6-7 
      // can't GC object references properly across the DOM-JS boundary 
      isNode = elem.nodeType, 

      // Only DOM nodes need the global jQuery cache; JS object data is 
      // attached directly to the object so GC can occur automatically 
      cache = isNode ? jQuery.cache : elem, 

      // Only defining an ID for JS objects if its cache already exists allows 
      // the code to shortcut on the same path as a DOM node with no cache 
      id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, 
      isEvents = name === "events"; 

     // Avoid doing any more work than we need to when trying to get data on an 
     // object that has no data at all 
     if ((!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined) { 
      return; 
     } 

     if (!id) { 
      // Only DOM nodes need a new unique ID for each element since their data 
      // ends up in the global cache 
      if (isNode) { 
       elem[ internalKey ] = id = ++jQuery.uuid; 
      } else { 
       id = internalKey; 
      } 
     } 

     if (!cache[ id ]) { 
      cache[ id ] = {}; 

      // Avoids exposing jQuery metadata on plain JS objects when the object 
      // is serialized using JSON.stringify 
      if (!isNode) { 
       cache[ id ].toJSON = jQuery.noop; 
      } 
     } 

     // An object can be passed to jQuery.data instead of a key/value pair; this gets 
     // shallow copied over onto the existing cache 
     if (typeof name === "object" || typeof name === "function") { 
      if (pvt) { 
       cache[ id ] = jQuery.extend(cache[ id ], name); 
      } else { 
       cache[ id ].data = jQuery.extend(cache[ id ].data, name); 
      } 
     } 

     privateCache = thisCache = cache[ id ]; 

     // jQuery data() is stored in a separate object inside the object's internal data 
     // cache in order to avoid key collisions between internal data and user-defined 
     // data. 
     if (!pvt) { 
      if (!thisCache.data) { 
       thisCache.data = {}; 
      } 

      thisCache = thisCache.data; 
     } 

     if (data !== undefined) { 
      thisCache[ jQuery.camelCase(name) ] = data; 
     } 

     // Users should not attempt to inspect the internal events object using jQuery.data, 
     // it is undocumented and subject to change. But does anyone listen? No. 
     if (isEvents && !thisCache[ name ]) { 
      return privateCache.events; 
     } 

     // Check for both converted-to-camel and non-converted data property names 
     // If a data property was specified 
     if (getByName) { 

      // First Try to find as-is property data 
      ret = thisCache[ name ]; 

      // Test for null|undefined property data 
      if (ret == null) { 

       // Try to find the camelCased property 
       ret = thisCache[ jQuery.camelCase(name) ]; 
      } 
     } else { 
      ret = thisCache; 
     } 

     return ret; 
    } 
+0

我想這個問題的種類「演變成」,[如何jQuery數據函數工作](http://stackoverflow.com/questions/5948099/jquery-data-how-to-work-question)。所以看起來真的沒有辦法解決這個問題,並且至少必須爲DOM節點設置單個屬性(例如散列名稱)。另外,從jQuery代碼中的註釋看來,只有IE6-7對GC有問題? – user17753 2012-03-16 17:49:49

+0

我想我現在得到它,每個綁定數據的DOM對象都被擴展爲包含數字索引的相同的任意隨機屬性名稱,並將其指向全局數組,該數組指向數據的鍵/值。所以沒有真正的辦法來擴展宿主對象至少一點點。 – user17753 2012-03-16 18:09:31

+0

是的。什麼時候不是IE6/7有問題的GC:P。如果你想了解更多關於這個,我建議閱讀http://javascript.info/tutorial/memory-leaks - 「jQuery反泄漏措施和泄漏」 – 2012-03-16 18:19:29

1

我發現,可能會給你一些JavaScript設計模式this文章想法。看看原型模式,這可以讓你跨實例重用方法。

+0

感謝您指出有趣的文章。令人失望的是'Object.create'非常新,所以我可能需要一些墊片來模仿舊版瀏覽器的行爲。雖然,在這種情況下,我的javascript對象確實沒有任何方法或默認屬性,我想重用。但是,當我有需要時,這可能會有所幫助。我知道我的部分代碼誤導了這一點。 – user17753 2012-03-16 18:59:37

相關問題