2012-03-16 90 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

相关问题