手机版

编写性能高效的javascript事件的技术

时间:2021-09-09 来源:互联网 编辑:宝哥软件园 浏览:

如何做一个高效的web前端程序,是我每次做前端开发都会不自觉考虑的问题。几年前,雅虎强大的前端工程师出版了一本关于提升web前端性能的书,在整个web开发技术领域引起轰动,让神秘的web前端优化问题成为街头的白菜。web前端优化成了菜鸟和大牛都能回答的简单问题。当整个行业都知道这个秘密答案的时候,现有的优化技术已经无法对你开发的网站进行质的飞跃。为了让我们开发的网站的性能比别人的网站更好,我们需要更独立地思考,储备更优秀的东西。

Javascript中的事件系统是我的第一个突破点。为什么是javascript事件系统?我们都知道web前端包含三种技术:html、css和javascript。如何将html和css结合起来,真的是一目了然:样式、类、id和html标签。没什么好谈的,但是javascript是如何切入html和css的中间,让它们融合的呢?最后发现这个突破点是javascript的事件系统。不管我们写的javascript代码有多长多复杂,最终都会通过事件系统反映在html和css中。所以我在想,既然事件系统是三者融合的突破点,那么一个页面中就会有很多事件操作,尤其是在如今越来越复杂的页面中。没有这些事件,我们精心编写的javascript代码只会随着刀枪入库,英雄无用武之地。既然页面上会有大量的事件函数,那么我们按照习惯编写事件函数时,会不会出现影响效率的问题呢?我研究的答案是真正的效率问题,也是一个严重的效率问题。

为了澄清我的回答,我想详细解释一下javascript的事件系统。

事件系统是javascript、html和css集成的切入点。这个切入点就像java中的主函数,所有的魔力都从这里开始。那么浏览器是如何实现这个切入的呢?我研究了三种方法,分别是:

方法1: html事件处理

Html事件处理是将事件函数直接写在html标记中,之所以称为html事件处理,是因为它与html标记紧密耦合。例如,以下代码:

复制代码如下:输入类型=' button' id=' BTN '名称=' BTN' onclick=' alert ('clickme!')'/

如果click事件函数比较复杂,这样写代码肯定会带来不便,所以我们经常在外面写函数,onclick直接调用函数名,比如:

复制代码如下:输入类型=' button' id=' BTN '名称=' BTN ' onclick=' btnclk()'/function btnclk(){ alert(' click me!');}

上面写的是一种优美的文风,所以现在很多人会不自觉地使用,但也许很多人不知道,后者的文风其实不如前者的文风稳健。这也是我前不久学习无阻塞加载脚本技术时遇到的一个问题,因为根据前端优化的原理,javascript代码往往位于页面底部。当页面被脚本阻塞时,html标记中引用的函数可能还没有实现。这时,当我们点击页面按钮时,结果会是“XXX函数未定义错误”。在javascript中,这种错误将被try and catch捕获,因此为了使代码更加健壮,我们将重写如下:

复制代码如下:输入类型=' Button' ID=' BTN '名称=' BTN ' OnClick=' Try { BTNClk();}catch(e){}'/

看到上面的代码是一个恶心的描述。

方法2: DOM0级事件处理

DOM0级别的事件处理是当今所有浏览器都支持的事件处理,不存在兼容性问题。看到这样一句话,会让每一个身为web前端的人都兴奋不已。DOM0事件处理的规则是每个DOM元素都有自己的事件处理属性,可以分配一个函数,比如下面的代码:

复制代码如下: var BTN DOM=document . getelementbyid(' BTN ');btndom . onclick=function(){ alert('单击我!');}

DOM0级事件处理的事件属性以“on event name”的方式定义,整个属性为小写字母。我们知道DOM元素是javascript代码中的一个javascript对象,所以从javascript对象的角度来理解DOM0级别的事件处理是非常容易的,比如下面的代码:

复制代码如下: BTN DOM . onclick=null;

那么按钮的点击事件被取消。

请看下面的代码:

复制代码如下: BTN DOM . onclick=function(){ alert('单击我!');} btndom . onclick=function(){ alert(' click me 1111!');}

后一个函数覆盖了第一个函数。

方法3: DOM2事件处理和IE事件处理

DOM2事件处理是标准化的事件处理方案,但IE浏览器开发了自己的一套类似于DOM2事件处理的功能,只是代码不同。

在解释第三种方式之前,我必须补充一些概念,否则我无法解释清楚第三种方式的内涵。

第一个概念是:事件流

在页面开发中,我们经常会遇到这样的情况。页面的工作区域可以用javascript中的document来表示,页面中有一个div,相当于覆盖了document元素。div中有一个button元素,相当于覆盖了文档,所以问题来了。当我们单击此按钮时,这种单击行为实际上不仅发生在按钮上,还发生在div和文档上。逻辑上,这三个元素可以触发点击事件,事件流就是描述上述场景的概念。事件流是指从页面接收事件的顺序。

第二个概念:事件冒泡和事件捕获

事件冒泡是微软公司提出的事件流问题的解决方案,而事件捕获是网景公司提出的事件流解决方案。它们的原则如下:

冒泡事件以div开始,然后是body,最后是document,事件捕获是反向的。相比之下,微软的解决方案更加人性化,符合人们的操作习惯,网景的解决方案非常尴尬,这是浏览器大战的恶果。网景慢了一步,用牺牲用户习惯的代码解决了事件流问题。

微软设计了一个结合冒泡事件的新事件系统,业内通常称之为ie事件处理。ie事件处理模式如以下代码所示:

复制代码如下: var BTN DOM=document . getelementbyid(' BTN ');btnDOM.attachEvent('onclick ',function(){ alert('Click Me!');});

在ie下,事件是通过DOM元素的attachEvent方法添加的。与DOM0事件处理相比,添加事件的方式从属性变为方法,因此我们在添加事件时需要将参数传递给方法。attachEvent方法接收两个参数,第一个参数是事件类型,事件类型的名称与DOM0事件处理中的名称相同,第二个参数是事件函数。使用方法的好处是,如果我们为同一个元素添加一个click事件,如下所示:

复制代码如下: BTN DOM . attach event(' onclick ',function () {alert ('click me!');});btnDOM.attachEvent('onclick ',function(){ alert('也单击我!');});

运行它,两个对话框正常弹出。该方法允许我们为DOM元素添加多个不同的点击事件。如果我们不想举办活动呢?我们做什么呢ie提供了一个用于删除事件的separate vent方法。参数列表与attachEvent相同。如果我们想要删除click事件,我们只需要传递与添加事件相同的参数,如下面的代码所示:

复制的代码如下: btndom。separate vent(' onclick ',function () {alert('也单击我!');});

运行它,后果很严重,我们很困惑,第二次点击还没有删除,这是怎么回事?前面我说过,删除事件应该传入与添加事件相同的参数,但是在javascript的匿名函数中,即使两个匿名函数的代码完全相同,javascript也会使用不同的变量在内部存储。因此,我们看到的现象无法删除click事件,因此我们的代码应该编写如下:

复制代码如下:varftn=function () {alert('也点击我!');};btnDOM.attachEvent('onclick ',ftn);btnDom . disconnectEvent(' onclick ',ftn);

这样,添加方法和删除方法指向同一个对象,所以事件被成功删除。这里的场景告诉我们,要有写事件的好习惯,即操作函数要独立定义,匿名函数不能作为习惯。

接下来是DOM2事件处理,其原理如下图所示:

DOM2是一个标准化的事件。使用DOM2事件,事件传输从捕获模式开始,即从文档到正文。div是一个中间点。当事件到达中间点时,事件处于目标阶段。事件进入目标阶段后,事件开始冒泡,最后在文档上结束。(捕获事件的起点和冒泡事件的终点,我指的是本文中的文档。实际情况是有些浏览器会从窗口开始抓取,到窗口结束冒泡。但是我觉得不管开发的时候浏览器本身是怎么设置的,关注文档更有意义,所以我这里一直用文档。).人们习惯于将目标阶段归类为冒泡的一部分,主要是因为冒泡事件在开发中的应用更加广泛。

DOM2事件处理非常麻烦。每次触发一个事件,所有元素都会被遍历两次,这比ie事件要糟糕得多。ie只需要遍历一次,但是遍历次数少并不意味着ie的事件系统效率更高。同时支持两个事件系统,从开发和设计的角度来看,会给我们带来更大的开发灵活性。从这个角度来看,DOM2事件还是非常可取的。DOM2事件的代码如下:

复制代码如下: var BTN DOM=document . getelementbyid(' BTN ');btnDOM.addEventListener('click ',function(){ alert('Click Me!');},false);var ftn=function(){ alert('也点击我!');};btnDOM.addEventListener('click ',ftn,false);

AddEventListener用于在DOM2事件处理中添加事件,它比ie事件处理多接收一个参数。前两个参数与ie事件处理方法的两个参数含义相同。唯一的区别是应该从第一个参数中移除上的前缀,第三个参数是布尔值。如果其值为真,将根据捕获模式处理事件,其值为假。事件根据冒泡进行处理。有了第三个参数,我们就可以理解为什么为了兼容这两个事件模型,在DOM2事件处理中应该运行两次事件元素。但是,这里请注意,无论我们选择捕获还是冒泡,两遍遍历都将始终执行。如果我们选择一种事件处理方式,那么在另一个事件处理过程中不会触发任何事件处理功能,这和空挡空转的原因是一样的。通过对DOM2事件方法的设计,我们知道DOM2事件在运行时只能执行两种事件处理模式中的一种,两个事件流系统不可能同时触发。因此,尽管元素遍历了两次,但事件函数永远不会被触发两次。注意,我说的不触发两次是指事件函数。事实上,我们可以模拟两个事件流模型的同时执行,例如下面的代码:

复制代码如下:btndom。addeventlistener ('click ',ftn,true);btnDOM.addEventListener('click ',ftn,false);

但是这种写法是多事件处理,相当于点击按钮两次。

DOM2还提供了删除事件的功能,即removeEventListener,编写如下:

复制代码如下: BTN DOM . removeeventlistener(' click ',ftn,false);

使用与ie事件相同的参数,也就是说,这些参数应该与定义事件的参数一致。但是,当使用removeEventListener时,不会传递第三个参数,默认情况下会删除冒泡事件,因为如果不传递第三个参数,默认情况下为false,例如:

复制代码如下:btndom。addeventlistener ('click ',ftn,true);btndom . removeeventlistener(' click ',ftn);

运行它,发现事件没有被成功删除。

最后,我想说的是,包括ie9及以上版本在内的ie9都很好的支持DOM2事件处理,ie8以下版本不支持DOM2事件。

让我们对三种事件模式进行如下比较:

比较1:方法1是一边,与其他两种方法比较

第一种方法是结合html和javascript,你有我,你也有我。为了深化这种方式,它是html和javascript的混合开发。用一个软件术语来说,就是代码耦合,代码耦合不好,非常不好。这是新手程序员的水平,所以第一种方式被打败了,另外两种方式被赢了。

比较2:模式2和模式3

都是这样写的,有时候真的很难说谁好谁坏。看了上面的内容,我们发现模式2和模式3最大的区别是一个DOM元素有一个事件,只有一个事件,而模式3可以让一个DOM元素有多个事件处理功能。在DOM2事件处理中,模式3也可以让我们精确控制事件流的方式,所以模式3的功能比模式2更强大,所以比下面的公式3略好。

以下是本文的重点:事件系统的性能问题。要解决性能问题,必须找到一个关键点。在这里,我从两个关键点来思考事件系统的性能问题,分别是:减少遍历次数和内存消耗。

第一个是遍历的次数,无论是捕获事件流还是冒泡事件流,元素都会被遍历,但它们都是从顶部窗口或文档开始遍历的。如果页面上DOM元素的父子关系比较深,遍历的元素越多,比如DOM2事件处理,遍历的危害就越大。如何解决这个事件流遍历问题?我的答案是否定的,这里有些朋友可能会有疑问,他们怎么会不在了呢?事件系统中有一个事件对象,即event。此对象有一种方法可以防止冒泡或捕获事件。我怎么能拒绝呢?这位朋友的问题很有道理,但是如果我们想用这个方法减少遍历,那么我们的代码就要处理父子元素和孙儿元素之间的关系。如果有很多嵌套的页面元素,这是一个不可能完成的任务,所以我的回答是我们无法改变遍历问题,只能去适应它。

看来减少遍历并不能解决事件系统的性能问题,所以现在要考虑内存消耗了。经常听人说C#好用,对web前端开发更好。我们可以在C#的IDE中将一个按钮直接拖动到页面上。按钮到达页面后,javascript代码会自动向按钮添加一个事件。当然,里面的事件函数是一个空函数,所以我认为我们可以这样在页面上放100个按钮。一个代码失败,会有100个按钮事件处理,超级方便。最后,我们给其中一个按钮添加混凝土。在javascript中,每个函数都是一个对象,每个对象都消耗内存,所以无用的99个事件函数代码必然会消耗大量宝贵的浏览器内存。当然,我们在真实的开发环境中是不会这样做的,但是在当今ajax盛行、单页开发疯狂流行的时代,一个网页上有很多事件,这就意味着每个事件都有一个事件函数,但是我们每次操作,只会触发一个事件,此时其他事件都在趴着睡觉,起不到任何作用,消耗电脑的内存。

我们需要一个解决方案来改变这种情况,现实中也有这样的解决方案。为了把这个方案描述清楚,我必须先补充一些背景知识。我在描述DOM2事件处理的时候提到了目标对象的概念。除了DOM2事件处理,在捕获事件处理和冒泡事件处理中也有目标对象的概念。目标对象是事件特定操作的DOM元素。例如,单击一个按钮就是目标对象,不管是哪种事件处理方法,event函数都包含一个事件对象,该事件对象有一个属性target,它总是指向目标对象,事件对象的另一个属性是currentTarget,它指向捕获或冒泡事件流向的DOM元素。从上面的描述中,我们知道事件流将流向文档,无论它是捕获事件还是冒泡事件。如果我们在文档中添加了一个点击事件,而页面上的按钮没有添加点击事件,那么我们就点击了按钮,我们知道文档上的点击事件将会触发。这里有一个细节,当click事件被触发时,事件的目标指向按钮而不是文档,所以我们可以这样编写代码:

复制代码如下: inputtype=' button ' ID=' BTN ' Name=' BTN ' Value=' button '/a href=' # ' ID=' aa ' aa/a document . addeventlistener(' click ',function(evt){ vartarget=evt . target;switch(target . id){ case ' BTN ' : alert(' button ');打破;案例“aa”:警报(“a”);打破;}},false);

运行它,我们发现效果和我们单独编写按钮事件时是一样的。但它的优势不言而喻。一个功能修复了整个页面的事件功能,没有一个事件功能是空闲的,很完美。这个方案还有一个专业名称:活动委托。JQuery的委托方法就是基于这个原理。事实上,事件委托的效率不仅体现在事件函数的减少上,还可以减少dom遍历操作。比如在上面的例子中,我们给文档添加了函数,文档是页面中的顶层对象,读取它的效率非常高。说到具体的对象事件,我们不是通过dom进行操作,而是使用事件对象的目标属性,这一切只能用一句话来概括:真的快,而且是无缘无故的快。

活动代表团也能给我们带来很好的副产品。所有使用过jQuery的朋友都应该使用live方法。live方法的特点是可以给页面元素添加事件操作,即使这个元素目前在页面上不存在,也可以添加它的事件。了解了事件委托机制后,对直播的原理有了很好的理解。事实上,jQuery的直播是通过事件委托来完成的,直播也是一种高效的添加事件的方式。

了解事件委托,我们会发现jQuery的bind方法是一种低效的方法,因为它使用的是原来的事件定义方法,所以要谨慎使用bind。事实上,jQuery的开发人员已经注意到了这个问题。新版本的jQuery有一个on方法,它包含了bind、live和delegate方法的所有功能。所以,我建议看过这篇文章的朋友,放弃之前的添加事件的方法,多使用on函数来添加事件。

活动委托还有一个好处。在上面的事件委托示例中,我将事件添加到文档中。这里我想做个比较。在jQuery中,我们习惯于将DOM元素事件的定义放在ready方法中,如下所示:

副本代码如下: $(文档)。ready(function(){ XXX . bind(' click ',function(){ });});

ready函数在页面DOM文档加载后执行,在onload函数之前执行。这种进步有很多好处,其中之一就是带来性能的提升。jQuery是一个事件定义,也是一个标准的实践。相信有些朋友一定把外面的一些事件绑定好了,最后发现按钮会失效。有时候这个无效的场景会在一瞬间变好。因此,我们经常忽略这个问题的原理,这个操作实际上是在加载DOM之前绑定事件,而不是用ready函数绑定事件。在这段时间内,很有可能页面上还没有构造出一些元素,所以事件绑定将无效。因此,准备定义事件的原因是为了确保在定义DOM元素的事件之前加载页面上的所有元素。但是,使用事件委托时,可以避免出现问题。比如事件绑定到文档,文档代表整个页面,可以说是最早完成加载的时间。因此,在文档上实现事件委托时,事件很难无效,浏览器也很难上报XXX函数未定义的问题。总结一下这个特性:事件委托代码可以在页面加载的任何阶段运行,这将让开发人员有更多的自由来提高网页的性能或效果。

好了,这篇文章写完了。晚安

版权声明:编写性能高效的javascript事件的技术是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。