tagName 与 nodeName

这两个概念,相信各位前端同学肯定都是比较清楚的,在JavaScript的开发中常常会用到,之前我一般都是用tagName,后来发现nodeName也能有一样的效果,但是他们直接的区别一直都不清楚,知道昨天看见了篇文章,讲得很清楚,因此就在这里翻译过来,也算加上自己的印象吧。

原文地址:http://aleembawany.com/2009/02/11/tagname-vs-nodename/

作者:Aleem Bawany

在JavaScript中检查HTML元素的名字,常常都要用到tagName和nodeName。通常情况下,两者都能达到同样的作用。如果你只支持A-grade浏览器的话,nodeName是一个更好的选择,但如果是你同样需要支持IE 5.5的话,那tagName却是更好的选择。

这里说一下tagName的两个问题:

  1. 在所有的IE浏览器中,一个注释节点(comment node)的tagName,总是会返回“!”。
  2. 对于文字节点(text node),tagName总是返回undefined,然而nodeName却返回“#text”。

但是nodeName也有自己的问题,但是影响不大:

  1. IE 5.5中,注释节点(comment node)的nodeName总是返回“!”,但它要比tagName好一些,因为nodeName只有在IE 5.5中,注释节点才会返回“!”,而其他版本IE均正常。
  2. IE 5.5中的文档元素(document element)以及属性节点(attribute node)的nodeName都失效。通常情况下,这些都不会造成什么问题,但是自己心里一定要有谱。
  3. Konqueror浏览器中使用nodeName的时候会自动忽略掉注释节点(comment node)。但是同样的,Konqueror和IE 5.5都不是A-grade浏览器

所以对于普通的JavaScript开发,还是应该坚持使用nodeName,因为它支持更广阔的应用场景,同时也有更好的向前兼容性。想想也知道,大家并不会因为它对于注释节点(comment node)的兼容性问题就放弃使用nodeName,相反,根本不用担心IE 5.5和Konqueror,因为他们的市场占用率都快趋近于0了。

KISSY事件模型研究(3)

通过KISSY事件模型研究(1)KISSY事件模型研究(2)两篇文章,相信大家对KISSY的事件模型已经有了大致的了解,下面我们来介绍这个模型中的最后一个部分:对于特殊类型的事件处理。

相信用JavaScript做过二级菜单的同学一定都有这样经历,在处理mouseover,mouseout这些事件时是十分纠结的,在《ppk谈JavaScript》中,ppk就给出了自己对于这两个事件的解决方案,当然我们知道如果只有IE浏览器,这时我想将会十分幸福,因为IE特有的mouseenter,mouseleave这两个事件此时对于我们是再好不过的选择,可是世界就是残酷的,有时爱捣乱的不是IE反而是其他W3C浏览器,可是这就是前端必须面对的兼容性问题啊。

在KISSY的事件模型中考虑到了对这类特殊事件的支持,在对事件对象的兼容性保证中,我们从之前一篇文章已经了解到,KISSY会利用浏览器提供的原生事件对象,按照W3C标准进行标准化,生成一个新的事件对象模型,这在一定程度上解决了事件模型的兼容性问题,但是对于之前根本不存在的事件类型,这样的处理就根本一无是处,就如刚刚提到的mouseenter,mouseleave这两个事件,在W3C标准浏览器中,他们就根本不存在,何谈事件对象。但是我们也知道,利用已提供的mouseout,mouseover这两个事件是可以模拟mouseenter,mouseleave的,KISSY在这里提供了一种叫做specail事件的机制来处理这些特殊的事件类型,即可由KISSY来模拟产生这些特殊的事件类型,而使用者自己模拟的困扰。

在KISSY的事件体系中,专门准备了一个special对象来存储这些特殊类型的事件,而添加这些特殊类型的事件就如添加一个“插件”一样简单,我们完全可以按照自己的方法去模拟一个不存在的事件,然后把它添加到我们的KISSY事件体系中,这样便可以方便地进行注册。

首先我们看一下这些特殊类型的事件是如何加入并组织到KISSY的事件体系中:

KISSY.Event

对于这些特殊类型的事件,我们可以按照以下方法定义并添加:

首先获得KISSY的事件容器;然后往事件容器中的special对象中添加我们准备定义的特殊类型事件的的名称;之后再讲用于模拟该特殊类型事件的原始类型事件写入fix属性中;同时我们要设置setup方法,该方法会传入我们已经标准化过的事件对象,这里我们可以根据这个特殊类型事件对这个事件对象做进一步处理,例如修改事件对象的type属性;最后,也是最重要的,我们要设置handle方法,这个方法是用于用已有类型的事件进行模拟的,主要是判断事件是否发生,发生时则调用已注册的侦听器,这个方法会覆盖默认的_handle方法,如这里纯粹是为一个类型事件重命名,我们不设置handle方法,则系统会调用默认的_handle方法进行处理。

至此,我们已经对KISSY事件模型做了一个整体的概述,其中大部分涉及到的只是一些设计思想和数据结构,具体的代码并没有具体提及,之后的KISSY分析系列则会加入更多代码方面的讨论,欢迎大家继续阅读!

KISSY事件模型研究(2)

距离上一篇文章刚好一周的时间,一周上班的忙碌,真的回来之后都不想动手写点儿东东了~还是应该向承玉学习啊,博客能够保持那么高更新频率~话说这周博客访问突增,多亏玉伯的文章推荐哈~所以以后得再接再厉了!闲话不多说了,继续KISSY时间模型的研究~

话说上一篇文章后面得到了很多网友的回复和支持,其中一位网友提到了一个很直接的问题,不过我们暂时先不说这个问题,继续我们上次的话题。上次我们说到用KISSY的方法注册函数的时候,同一类型的DOM事件都是交由一个事件代理进行统一处理的,那么这次讨论点就从这个事件代理出发。

说到事件代理,相信对JavaScript有一定了解的人肯定都很熟习,说简单了,这个事件代理就是一个事件处理函数,我们在注册事件的时候,也是把它注册到事件目标对象上的。这里就涉及到事件的冒泡和捕获,感兴趣的同学可以先google了解一下这些基本概念。那为什么要用事件代理呢?其实这个话题完全可以写一篇博客自习研究,我这里就大概说一下我自己的想法。让我们先假设有一个div,其中包括了很多a,我们要做的就是对每一个a的click事件做不同处理(注意,这里有两个关键点,一个是“每一个a”,另一个是“不同处理”)。遇到这样一个问题,我想最开始的想法一定是对每个a注册一个函数,但是仔细想想,那么多a,以及那么多函数,这是多么棘手的一个问题啊?如果是统一的处理还很好办,大不了一个循环把所有a注册上一个处理函数,这样问题不就简单了,只是需要在这唯一一个处理函数里面进行判断做不同的处理罢了,这的确简化了我们最开始的想法,但是现在还存在一个可恶的循环啊,循环注册同一个事件处理函数,感觉真的好浪费啊!那么我们很自然的相当能不能就把这唯一一个事件处理函数注册到少量的元素上来达到目的呢?这大概就是事件代理的雏形吧,根据事件冒泡的冒泡性质(注意,并不是所有事件都会冒泡),我们可以把这唯一一个事件处理函数注册到div这个元素上,这时候我们只用往一个元素上注册唯一个事件处理函数,这不大大简化了我们最开始的想法?

有了事件代理的概念,我们再回过头想想我们的KISSY在注册事件的时候,对于同一类型的事件处理函数可以有很多,是不是很类似我们刚刚说的div中的a呢?但是我们的KISSY在注册这些事件的时候,不是每一个事件处理函数都是直接注册到事件目标对象上的,就像我们不准备用循环注册的方式在注册没一个a的处理函数,我们只是把他们暂存到侦听器里面(实际上,侦听器就是一个简单的数组),排成队列,然后依次调用即可,那我们div上的事件代理就像是我们KISSY中,同一类型的那个唯一的事件处理函数,我们只将它直接注册到事件目标对象上。请看下图:

event_target_object

我们往事件目标对象上注册了三个事件处理函数,但是这三个处理函数只是存储在侦听器这个中,而侦听器连接到事件代理上,真正注册到事件目标对象上的处理函数只有这个事件代理函数。因此对于DOM事件,我们需要用DOM方法,往事件目标对象上注册事件,处理函数就是这个事件代理,当事件发生时,就会调用这个事件代理进行处理,在事件代理中,我们只需依次从侦听器中调用用户注册的时间处理函数,就可以完成所有用户注册的所有事件处理函数的调用。需要特别说明的是,我们这里的事件代理,使用事件类型进行区分的,也就是说每个类型的事件都有一个事件代理以及一个侦听器数组,这样的划分粒度,即保证了函数处理、存储的简洁性,也保证了事件较高的可维护性,例如:我们可以简单滴取消所有某一类型的事件处理函数,这个工作对于直接使用DOM方法注册的事件处理函数,很难简单做到,相信诸位都有深刻体会。

说了这么说事件目标对象,那么我们应该自习分析一下这个概念。从字面上上理解,可以分为事件目标,和事件对象这两个方面,同时这正是KISSY源代码中对于事件相关文件的组织方式。

对于事件目标,其主要特点就是能够注册事件、撤销注册以及触发事件,对于DOM元素,除了text/commen两种类型的节点,其他都满足这个特点所有的DOM Node都满足这个特点,包括text/comment节点,只是这两种节点在实际应用中都不大实用(感谢玉伯指正),例如可以用addEventListener等方法注册一个事件处理函数,可以用removeEventListener这个方法撤销一个事件处理函数,所有的DOM事件都可分为UI Event、MouseEvent、KeyEvent,他们在浏览器内部都有相应的触发机制,构成了事件触发,因此DOM元素天生就是做事件目标的料根据DOM规范描述,EventTarget就是一个Interface,包含 addEventListener,removeEventListner,dispatchEvent 三个方法,因此,实现了这3个方法的对象,就是 EventTarget,DOM Node就是这样的(再次感谢玉伯指正);而我们要实现自定义函数,那么自定义事件目标就必须满足以上刚刚说的三个方面,因此在KISSY的事件模型中,自定义事件和DOM事件做了统一,只要自定义事件完成了三个相关函数的实现,就可以称之为事件目标。

事件对象,主要就是事件发生时,携带事件相关信息的对象,我们知道在DOM事件中,自身就有这个对象,只是这个对象存在诸多的兼容性问题,最基本的一个就是在IE8以下(包括),DOM事件发生时,这个事件对象是存储在window.event中,而W3C标准的浏览器,则是作为默认参数传递给事件处理函数,因此这类的不一致情况,导致了诸多了事件对象的兼容性问题,那么KISSY中为了处理这类的不兼容性,就用专门的fix对事件对象进行了修复,并提供了几个方便的事件对象函数的封装,例如阻止默认行为(preventDefault)、停止传播(stopPropagation)、终止事件(halt,包括停止事件传播和阻止默认行为)、立即停止传播(stopImmediatePropagation,与stopPropagation的主要区别是,该方法不仅阻止事件传播,还阻止之后侦听器中后续函数的发生)。我们的事件代理在事件触发时调用,其做的第一件事儿就是取出事件对象,然后传递给EventObject构造函数,构造出经过修复后的事件对象,这里的修复主要是参照W3C标准进行。文章最开始说到有一个网友提到了一个问题,其实这个问题就是和这个有关,他认为每次事件发生时,调用事件代理都需要构造一个EventObject,必然会影响性能,因此我也对这个性能有所担心,但是考虑到通常的事件并不会很高平率的触发,因此感觉这个性能问题影响不大,因此做了以下测试页:

请猛击这里查看DEMO

这个DEMO里,我用createEvent方法,创建了一个click事件,然后把这个事件分配给一个checkbox(以让大家能看见这个click的确发生了),一方面我们用传统的方法注册事件,不处理事件对象;另一方面,我们用KISSY的方法注册事件,这里的事件处理函数主要是处理一个进度条,当进度条完成后,记录消耗时间。因此,我们可以模拟大量点击事件,来考虑事件处理的性能问题,同时对于大量点击的模拟,我们加入一个延迟,可以模拟事件频发程度。对于这个DEMO,我们只能用非IE的Firefox和Chrome等浏览器测试(Opera也不支持),对于具体结果,大家可以先自己测试,观察一下。

点击Start按钮,可以看见右侧原本为选中的复选框已经选中,表示我们模拟的click事件已经有效了,我们如果用delay值为0的设置开始测试,会发现传统方式完成1000次点击、处理的时间在300ms左右,而KISSY方式消耗时间则在500ms以内(Chrome 6.0.472.25 dev),还是有尽40%的性能差异;当delay值设置增大时,两者的差距越来越小,因此可以看见两种方式的确有性能差异,但是当delay时间值增大,即浏览器有时间进行更复杂运算时,两者的差异已经很小,因此EventObject的创建有性能消耗,但是如果函数的调用没有如此频繁时及有足够的delay时间时,两者基本没有性能差异。因此可以得出结论,如果我们在实际使用中,KISSY的事件处理方式并不会造成较大的性能影响,我们用的主要事件很少会频发次数很高,今后可以再详细测试一下类似MouseMove的事件,同时可能还要考虑到具体的JavaScropt的具体运行环境。

今天主要的谈论集中在KISSY事件模型中的事件处理方式以及事件目标对象的概念,下次我们继续说说KISSY对一些特殊事件的处理方式,比如我们熟知的只有IE系列才拥有的MouseEnter/MouseLeave事件等,到此,我们对整个KISSY的事件模型和体系应该有大致了解,如果感兴趣的话,欢迎fork我们的源代码进行属于自己的开发,说不定你优秀的代码也能merge到KISSY的主分支中!

- EOF -

update. 2010.08.10 感谢网友hi,给出了支持IE测试的地址,欢迎强势围观。

我在你的demo上修改了一点点,可以支持ie了 代码见 http://www.nci365.com/test/e1.html
http://www.nci365.com/test/e2.html

e1.html和e2.html只有一句不同,见第119和120行
这两个比较下很有意思,让事件触发不是同一时刻并发,而是每个事件错开1ms触发,得到的测试结果就迥然不同。

KISSY事件模型研究(1)

最近做到一个需求,是基于KISSY完成核心代码的构建,对其中的事件模型很是有好感,借今日有空闲时间,正好研究研究,做了一下记录,供大家学习参考,有误之处还望大家及时指出!

首先让我们想一想,如果要给一个div元素添加一个click事件处理函数,常用的方法有两种吧:
一种:

div.addEventListener('click', foo, false);

或者:

div.onclick = foo;

这两种传统的事件注册方法,都涉及到三个要素,第一,要注册事件的元素target - div;第二,要注册的事件类型type - click;第三,要注册的事件处理函数fn - foo。这三个便是一个事件注册中需要关注的三个主要方面,我们称之为事件三要素。

那么我们就从刚刚所说的事件三要素中,考虑kissy是如何给事件目标对象注册事件的。 不过这里我还要啰嗦一句,我们这里所说的事件目标对象是在kissy中的一个特殊对象,除text/comment节点元素之外的所有DOM元素都是事件目标对象,当然,我们也可以有自己的事件目标对象,但它必须特殊处理才能成为事件目标对象,这个我们可以稍候再说。

1. 事件目标对象:这个即事件三要素中的target,它是整个事件注册过程中的主体,没有了它,一切无从谈起,因此我们首先要判断它是否是一个合法的事件目标对象,这里的要求很简单,只要是非text/comment节点元素的一切对象,都可以充当事件目标对象(不过就像我之前试过的,我们的自定义对象需要做一些特殊处理)。有了这个事件目标对象,我们要怎么处理它呢?自然是注册指定类型的事件处理函数,但是这里变遇到了个问题,我依然要像从前一样把事件处理函数直接注册到这个事件目标对象吗?大家都知道直接注册上去的弊端,其一就是无法统一管理,其二就是容易导致内存泄漏。对于这样两个严重的弊端,我们当然不能视为不久,因此这里我要隆重像大家介绍一个概念——事件缓存。

这里的事件缓存其实应该称为事件仓库更为且当,因为其中存放了所有使用我们特定事件注册方法注册的事件处理函数。正如刚刚所说的一样,那为什么会有事件仓库这个概念呢,首先是考虑到对事件的统一管理上,所有我们添加过的事件都能在仓库里面找到,而不像从前一样,事件都是独立分开,无法统一管理的,这里的管理,主要指的是对事件的添加、删除和处理;其二,事件仓库能够帮助我们减少内存泄漏的危险,从此,我们的事件不是直接注册到一个元素上,而是通过一个钩子,连接元素和事件仓库,所有的事件都存放在这儿,自然不会产生内存泄漏。对于刚刚说到的这个钩子,就是我们所说的事件目标对象的事件id,这个唯一的id标识于事件目标对象上,则可在事件目标对象和事件仓库中建立唯一连接。

这里设计的事件仓库数据模型如下图所示:

event_cache

这里对于事件仓库的组织结构很清晰,每个唯一事件id对应于唯一的事件目标对象,target即事件目标对象,events则存放该事件目标对象所有类型的事件,对于其中一个类型的事件,包括一个处理函数即事件代理和数个侦听器,每个侦听器中存放用户注册的事件处理函数和作用域。

既然已经了解了事件缓存的概念,那么需要在事件缓存和事件目标对象建立,就是刚刚提到的那个钩子,我们的事件注册函数自然会帮我们初始化并维护一个这些唯一的id用于建立事件缓存和事件目标对象的连接。则从此,我们已经把事件目标对象存放到事件缓存中,到此,事件目标对象的处理也算基本完成了。

2. 事件类型:下一个我们要考虑的要素就是我们要注册的事件类型。对于事件目标对象,在其上可发生诸多事件,我们就需要在上面注册指定类型的事件,然后当该事件发生时用相应的处理函数去处理,这个概念相信在DOM节点上大家都很清楚,正如文章开头给出的例子,我们就是在div这个事件目标对象注册了click这个事件类型的事件处理函数foo。那么这个事件类型怎么和事件仓库连接起来呢?再回过头去看那个数据模型图,那个events对象正是用来存储我们注册到事件目标对象的所有事件的对象。如果我们添加到其中的某一类型事件还不存在,就需要对该类型的事件进行初始化,包括建立一个统一事件代理,初始化侦听器数组。到此,我们的事件注册函数,对于事件类型的处理部分就完成了。

3. 事件处理函数:这一要素又是整个事件注册的核心要素。这里我就需要考虑到我们的自定义事件目标对象和传统的事件目标对象。传统的事件目标对象一般指DOM节点元素,这些对象有原生的事件注册方法(这里要指出的是kissy里面还有一个Node类型的DOM节点元素包装,其本质还是DOM节点元素),我们需要为他们注册侦听器,用我们统一的事件代理进行处理;而我们的自定义事件目标对象则不需要单独的事件注册,因为这些自定义事件都是需要“手动”触发的,因此不需单独的监听器绑定。最后已经往事件目标对象注册了侦听器,交由统一的事件代理,那么我们还需把我们自己需要绑定的事件处理函数,压入到我们的事件侦听器数组listeners里面去。这里说一下基本的事件处理思路,对于传统的事件目标对象,我们都往同一种类型是事件上注册唯一一个统一的事件代理,然后事件触发后有其统一处理,而我们自己的事件处理函数呢,存放于events对象的侦听器数组listeners里,在事件触发时,我们可以在事件代理再触发每个侦听器函数,这样就完整地模拟了传统事件目标对象的事件触发-处理;而对于我们的自定义事件目标对象,在手动调用fire方法触发事件时,就可以触发每个事件侦听器函数。

到此,一个完整的事件注册过程变完成了。其他事件模型的相关函数之后再介绍哈~

- EOF -