javascript进阶修炼之二——DOM,事件及定时器

获得DOM元素的引用

首先注意以下几点:
 
  • 注意document.getElementById,任何依赖于这个方法的代码都会成为IE怪异行为的牺牲品。因为在IE中,这个方法也会通过name属性来寻找匹配的元素。
  • 记住,搜索范围越窄,选择速度也就越快。
  • 原始的DOM API并不适合实际的元素访问操作,因为它们适用于节点,而不是元素。原始的DOM API会让你陷入到空白节点,注释节点,文本节点等节点的泥潭中去。
通过ID属性获得对应的元素
 
document.getElementById("elementId") ;//原始的W3C DOM
$("#elementId"); //jQuery
$("elementId"); //Prototype,MooTools
Ext.getDom("elementId") ;//Ext JS3
dojo.byId("elementId"); //Dojo
 
通过XPath/css选择来获得元素
 
document.querySelectorAll("selectors") ;//Native(如上)
$("selectors") ;//jQuery
$$("selectors") ;//Prototype,MooTools
dojo.query("selectors") ;//Dojo
Ext.query("selectors") ;//Ext JS3
 

动态修饰内容

 
//设置元素的样式
$(element).css('prop','value');
$(element).css({'prop1':'value1','prop2','value2'});
 
//获取元素的样式
$(element).css('prop') ;
 

修改元素的内容

 
//更新元素的全部内容
$(element).html("<p>new internal HTML</p>");
$(element).text('The <div> and <span> element carry no inherent semantics.');
 
 

在DOM加载完成后运行脚本

 
//在DOM加载时执行指定脚本
$(fx)
 

监听及停止监听事件

 
//在某个元素上监听某个事件
$(elementOrSelector).bind('event',handlerFx);
//在多个元素上监听某个事件
$(elements).bind('event',handlerfx);
//停止监听
$(elementOrSelector).unbind('event',handlerFx);
 

利用事件委托

 
请用心记住这句话:优先使用事件委托
submit,focus,blur这些事件不会冒泡。
直到jquery 1.4版本开始,live()方法才开始本质上灵活地支持事件委托,而且不会带来性能问题。
不过现在已经改为on()事件了,或者使用delegate()方法也可以,差别是on()默认绑定到document根节点上,绑定到非默认document上也可以,
 
$('a',$('#container')[0]).on('click',function(){alert('That tickles!')})
 
使用delegate的写法略有差别:
 
$('#container').delegate('a','click',function(){alert('That tickles!')})
 
后者实际上要快过前者,因为前者首先要扫描整个的文档查找所有的$(‘a')元素,把它们存成jQuery对象。尽管live函数仅需要把'a'作为串参数传递以用做之后的判断,但是$()函数并未“知道”被链接的方法将会是.live()。

而另一方面,delegate方法仅需要查找并存储$(document)元素。
一种寻求避开这一问题的方法是调用在$(document).ready()之外绑定的live,这样它就会立即执行。在这种方式下,其会在DOM获得填充之前运行,因此就不会查找元素或是创建jQuery对象了。
 
仅支持CSS选择器
最后一点,live方法有一个非常大的缺点,那就是它仅能针对直接的CSS选择器做操作,这使得它变得非常的不灵活。
欲了解更多关于CSS选择器的缺点,请参阅jQuery .live() and .die()。
 
为什么选择.on()(.live())或.delegate()而不是.bind()
 
毕竟,bind看起来似乎更加的明确和直接,难道不是吗?嗯,有两个原因让我们更愿意选择delegate或live而不是bind:
1. 为了把处理程序附加到可能还未存在于DOM中的DOM元素之上。因为bind是直接把处理程序绑定到各个元素上,它不能把处理程序绑定到还未存在于页面中的元素之上。
2. 如果你运行了$('a').bind(…),而后新的链接经由AJAX加入到了页面中,则你的bind处理程序对于这些新加入的链接来说是无效的。而另一 方面live和delegate则是被绑定到另一个祖先节点上,因此其对于任何目前或是将来存在于该祖先元素之内的元素都是有效的。
3. 或者为了把处理程序附加到单个元素上或是一小组元素之上,监听后代元素上的事件而不是循环遍历并把同一个函数逐个附加到DOM中的100个元素上。把处理 程序附加到一个(或是一小组)祖先元素上而不是直接把处理程序附加到页面中的所有元素上,这种做法带来了性能上的好处。
 
停止传播
 
最后一个我想做的提醒与事件传播有关。通常情况下,我们可以通过使用这样的事件方法来终止处理函数的执行:
 
$('a').bind('click',function(e){
e.preventDefault()
e.stopPropagation()}
)
不过,当我们使用live或是delegate方法的时候,处理函数实际上并没有在运行,需要等到事件冒泡到处理程序实际绑定的元素上时函数才会运行。而到此时为止,我们的其他的来自.bind()的处理函数早已运行了。
 

将行为和自定义事件解耦

监听一个自定义事件
//jQuery——通过多余的参数传入额外数据
$(elementOrSelector).bind('event',handlerFx);
 
触发一个自定义事件
//jQuery触发自定义事件
$(elements).trigger('event') ;
$(elements).trigger('event',{foo:'bar',baz:42});
$(elements).trigger('event',['bar',42]);
 
//给element绑定hello事件
element.bind("hello",function(){
    alert("hello world!");
});
       
//触发hello事件
element.trigger("hello");
 

 这段代码这样写似乎感觉不出它的好处,看了下面的例子也许你会明白使用自定义事件的好处了:

    我们已一个选项卡的插件为例:我们让ul列表来响应点击事件,当用户点击一个列表项时,给这个列表项添加一个名为active的类,同时将其他列表项中的active类移除,以此同时让刚刚点击的列表对应的内容区域也添加active类。

 
HTML:
<ul id="tabs">
    <li data-tab="users">Users</li>
    <li data-tab="groups">Groups</li>
</ul>
<div id="tabsContent">
    <div data-tab="users">part1</div>
    <div data-tab="groups">part2</div>
</div>
 
jQuery:

    
$.fn.tabs=function(control){
    var element=$(this);
    control=$(control);
    element.delegate("li","click",function(){
        var tabName=$(this).attr("data-tab");
         //点击li的时候触发change.tabs自定义事件  
        element.trigger("change.tabs",tabName); 
    });
         
    //给element绑定一个change.tabs自定义事件
    element.bind("change.tabs",function(e,tabName){
        element.find("li").removeClass("active");
        element.find(">[data-tab='"+ tabName +"']").addClass("active");
    });     
    element.bind("change.tabs",function(e,tabName){
        control.find(">[data-tab]").removeClass("active");
        control.find(">[data-tab='"+ tabName +"']").addClass("active");
    });
    //激活第一个选项卡  
    var firstName=element.find("li:first").attr("data-tab");
    element.trigger("change.tabs",firstName);
                 
    return this;
};
 
    由于插件位于jQuery的prototype里面,因此我们可以基于jQuery实例来调用:
$("ul#tabs").tabs("#tabsContent");
 
    从上面的例子我们可以看到使用自定义事件回调使得选项卡状态切换回调彼此分离,让代码变得整洁易读。
 

模拟后台处理

 
在网页进行耗时操作时,比如需要后台处理技术
这时候javascript引擎会按如下方式执行你的代码。
  • javaScript本质上是单线程的
  • 你的javascript运行线程实际上和你的页面共享了同样的资源。这也意味着,当你的javascript代码运行的时候,任何页面渲染都不会发生。新的内容不会出现,内容无法重排,甚至被其他窗体所遮挡住的页面也无法被重绘。。。总之,什么都做不了。
所以,如果你的页面在执行一些密集的计算,那么直到处理结束前页面都不会有响应。这通常意味着整个浏览器都会失去响应。为了防止这种情况发生,一些浏览器提供了“中断运行时间过长的脚本”的功能。而其它浏览器,例如“chrome”,则会为每个页面开一个独立的线程,以处理这个问题。
 
如果不使用Web Workers,你就需要使用一些伪并行处理的技巧,此类技巧一般依赖于全局window对象提供的一对方法——setTimeout()和clearTimeOut()。
 
这些技巧的思路是把一个大型任务分解成若干个小步骤,然后一边执行这些步骤,一边记录任务进度,并在固定的时延对这些步骤进行调度。当一个步骤完成之后,经过一段时间再启动下一个步骤。在这段空闲的时间内,浏览器会恢复对页面的控制,因此就可以正常地处理页面行为,并运行其他待执行脚本。
 
尽管这个技巧适合用于密集计算处理任务,但是他并不适合那些需要平滑过渡的行为,比如视觉效果,因为定时器的精度很差(在25ms-500ms变化),在这种情况下,你需要使用一个具有精确固定间隔的定时器。
 
调度及停止代码的执行
 
利用定时器模拟后台处理需要两个核心的方法:
var handler = window.setTimeout(callback,intervalInMs);
window.clearTimeout(handler);