我想写一些纯粹的javascript来更好地理解它(我在“实践中”意识到jQuery等框架更加建议和适用,但这并不是关于如何使用框架,更多关于如何使用javascript工作和最佳实践)。
无论如何,我写了一些简单的javascript代码。我想创建一组按钮组,每次从一组{on,off}有一个状态,每个状态将映射到进入该状态时要触发的相应函数。主集中的每组按钮一次只能包含处于打开状态的一个按钮。这个概念类似于单选按钮的概念。 为什么不使用单选按钮呢?从语义上来说,它只是假设某些控制元素的按钮,但无论哪种方式,我想我可以,但问题不是真的那个。
问题是,为了解决这个问题,我在Javascript中通过button
向特定的id
元素添加了很多自定义属性。我正在做一些研究,发现这个question和这个question,关于在DOM节点(对象)上使用自定义属性。他们似乎主张反对这种做法,甚至可以说这样做会导致潜在的内存泄漏,具体取决于浏览器的实现。
但是,对于我创建的每个按钮,我需要跟踪很多属性,如果我扩展这个脚本,我可能需要添加更多。那么将它们存储在DOM节点上的最佳方法是什么,但仍然跟踪所有内容并能够在附加函数中使用 this 等等?
对我来说,如果不至少将一个井名间隔对象的引用存储到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>
更新
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>
更新2
我发现通过使用闭包的强大功能,我真的只需要存储一个“特殊”属性,即按钮所属的组。
我将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 :(得分:1)
我发现{j}设计模式的this文章可能会给你一些想法。看看Prototype Pattern,它允许您跨实例重用方法。
答案 1 :(得分:1)
您要么扩展对象(对主机对象不好),要么像jQuery那样包装对象,使用包装对象来标识哈希表中的关联数据。本质上,您散列DOM节点并在哈希表中查找关联数据。当然,您仍然需要扩展主机对象,但是只添加一个您知道在浏览器中添加相当安全的属性,而不是一组任意属性。如果检查具有关联数据的元素,您可能会看到类似element.jQuery171023696433915756643的内容,其中包含该元素的内部存储索引。如果您感兴趣,我建议您阅读jQuery源代码,尤其是data()函数
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;
}