在JavaScript里,通常要做的一件事是绑定事件,比如用户在页面的点击、滚动等,然后执行注册的回调函数,这样就响应了用户的某种行为。简单的例子如下:
$('button').on('click', function() {
alert('hello');
});
在用户每次点击页面上的按钮时,弹出一个对话框显示‘hello’。 在有些情况下,我们期望页面上的一些元素响应用户同样的动作,举个例子。在用户点击列表的每一项时,将其内容显示在div#data-show里。
<ul id="data-list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
…
<li>100</li>
</ul>
<div id="data-show"></div>
可以这么做:
$('#data-list li').on('click', function() {
$('#data-show').html($(this).html());
});
其实是给列表的每一项(100个)分别绑定了点击事件。这样做的弊端在于,增加了内存,因为$(’#data-list li’)里有100个li对象。同时降低了代码性能,因为$(’#data-list li’)会搜索ul#data-list下所有的li元素。
答案是使用事件委托!
$('#data-list').on('click', 'li', function() {
$('#data-show').html($(this).html());
});
将li元素的点击事件委托给其父元素ul。这么做之所以行得通,是因为事件具有冒泡的特点,当内层元素的某个事件被触发,事件会一级一级冒泡到更外层元素。当外层元素被绑定事件且被触发时,判断事件的来源即event.target是否是目标元素li,如果是就执行回调。上面的代码等价于:
function showText(text) {
$('#data-show').html(text);
}
$('#data-list').on('click', function(event) {
var $target = $(event.target);
if ($target.is('li')) {
showText($target.html());
}
});
除了提高性能和节省内存的好处外,事件委托的另一个好处在于,页面动态变化后,不需要重新检索元素和绑定事件。上例中,如果通过AJAX向列表增加新项,新添加项仍能响应用户点击。
可能存在这样的需求,页面上的多个元素(不同的),会响应同样的用户行为,比如一个按钮和一个链接,均须响应同样的行为。这种case还有可能存在页面之间。举个例子。
Page 1:
<button class="primary">See more</button>
<a class="secondary">See details</link>
Page 2:
<input type="button" class="third" value="Check more"/>
JS代码势必会这样:
$('.primary').add('.secondary').add('.third').on('click', callback);
如果又有其他元素也要响应同样的行为,需要修改以上JS代码,而使其变得更长。是否有更好的方式?看下面这个例子。
JS code:
var events = (function() {
var list = {};
return {
on: function(actionName, fn) {
if (!(typeof actionName === 'string' && typeof fn === 'function')) {
throw new Error('Invalid args');
}
list[actionName] = fn;
},
trigger: function(actionName, data) {
var callback = list[actionName];
callback && callback.call(null, data);
}
};
}());
$(document).on('click', '.delegated-action', function(event) {
var $el = $(this), actionName = $el.data('actionName');
if (!actionName) {
return;
}
var evt = {
$event: event,
$self: $el,
data: $el.data('actionData')
};
events.trigger(actionName, evt);
});
function showIndex(event) {
alert(event.data.index);
}
events.on('see-more', showIndex);
这个例子中,将所有含有样式类delegated-action的元素上的点击事件委托给了document。对象events存储用户通过events.on定义的所有动作和回调。在页面中,只要给元素加上属性class=”delegated-action”和data-action-name=”see-more”,该元素就能响应用户的点击动作了。若需要给回调传入数据,可以将数据以JSON的形式绑定在data-action-data属性上,而不需要修改JS代码。
HTML code:
<a class="delegated-action" href="javascript:" data-action-name="see-more" data-action-data='{"index": 1}'>See more</a>
<button class="delegated-action" data-action-name="see-more" data-action-data='{"index": 2}'>See more</button>
<input class="delegated-action" type="button" data-action-name="see-more" data-action-data='{"index": 3}' value="See more"/>
这种方式很适合页面上通用组件或行为,而不用关心元素的类型、ID、命名等,只需要在HTML里添加指定的样式类,定义data-action-name和data-action-data属性即可。文章只拿click事件来描述例子,读者有兴趣可以扩展到代码以适合其他事件。