深入研究WINDOW.EVENT对象

作者:邓楠乔 时间:2012-04-26 16:31:58 

本周的豆知识分享就来深入研究一下window.event对象。请先看看下边的代码片断。


 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

    document.getElementById('btn').onclick = function (e) {

        if (typeof e == 'undefined') e = window.event;

        var target = (typeof e.target == 'undefined' ? e.srcElement : e.target);

        alert(target);

    }

// ]]>

</script>

上边是一种事件处理函数的常见写法,并且很好地处理了跨浏览器兼容问题。因此,变量target在不同浏览器下都正确地指向了被点击的button元素。但是我把代码稍微改成下边这种样子后..



 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

document.getElementById('btn').onclick = function (e) {

if (typeof e == 'undefined') e = window.event;

setTimeout(function () {

var target = (typeof e.target == 'undefined' ? e.srcElement : e.target);

alert(target);

}, 1000);

}

// ]]>

</script>

我写了一个匿名函数,并让它在1秒后执行。由于我构造了一个闭包,1秒后执行的匿名函数也应该能正确地访问到变量e,而变量target也应该正确地指向button元素。然后我用比较流行的浏览器测试了一下上边的代码,最新版本的Firefox、Chrome和Opera下一切都如同预期,可是在IE系列(6、7、8)下,我只得到了一个JS错误——找不到成员。好吧,我需要分析一下原因了。因为这段代码只在IE下有问题,而且经过一些试验后我也排除了闭包引起问题的可能性,因此我把上边的代码简化成如下这种样子,以便突出问题的本质。



 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

document.getElementById('btn').onclick = function () {

setTimeout(function () {

alert(window.event.srcElement);

}, 1000);

}

// ]]>

</script>

不辜负期望,以上代码同样产生了JS错误。然后我把alert(window.event.srcElement);改成了alert(window.event),这次终于没有错误了,而弹出的消息框里的内容是null。然后我又去翻了翻MSDN Library,看到了下边这段描述:

The event object is available only during an event—that is, you can use it in event handlers but not in other code.

也就是说,与其它浏览器在事件触发之后为每个事件创建一个单独的Event对象相对,IE的所有事件公用一个Event对象,也就是window.event。因此为了避免冲突,针对某个事件的window.event对象只在该事件的事件处理函数的执行过程中有效,一旦事件处理函数执行完了,window.event就被IE设置为null了。

到此为止,似乎问题的原因已经很明了了。但是细心的同学也许会反驳,IE其实也是为每个被触发的事件创建一个单独的Event对象,只不过每次都通过window.event来引用新生成的对象。因此我写了下边这段代码来验证这种观点。


 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

var lastEventObj = null;

document.getElementById('btn').onclick = function () {

if (lastEventObj == null)

lastEventObj = window.event.srcElement;

else

alert(lastEventObj === window.event.srcElement);

}

// ]]>

</script>

按钮在第一次被点击时,该事件的Event对象被lastEventObj变量所引用。因此就算在按钮第二次被点击时window.event被设置为新的Event对象的引用,仍然可以使用lastEventObj变量来访问到第一次点击事件时的Event对象。如果IE在每次事件被触发时都创建一个新的Event对象,那么lastEventObj === window.event.srcElement应该返回false。如果IE为所有事件公用一个Event对象,只是在每次事件触发时重新设置该对象的属性的话,lastEventObj === window.event.srcElement应该返回true。经过测试,返回值是false。

问题开始变得有些诡异了。既然每次事件被触发时的Event对象都是不同的,在本文的第二段代码里边,的确是在变量e中保存了对Event对象的引用的。即使事件处理函数执行完毕后window.event被设置为null,在1秒后自动执行的匿名函数中仍然应该可以使用变量e来访问先前的Event对象,但是为什么会产生JS错误呢?问题的原因在这时再此变得扑朔迷离了。我做了一些试验后得到了一些惊人的结论,以下我直接列举出一些事实。

1、window.event对象不是真正的JavaScript 对象。

按照ECMAScript规范,JavaScript中只应该存在有两种数据类型——值类型和对象。而一切对象都应该衍生于Object对象,因此Object.prototype中定义的方法,例如toString,应该在一切对象上都可以调用。那么我们来看看下边的代码:


 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

document.getElementById('btn').onclick = function () {

alert(typeof window.event.toString);

}

// ]]>

</script>

点击按钮后,弹出的消息框里竟然显示的是undefined。由此可见,任何标准的JavaScript对象应该包含的成员却在window.event对象上消失了。既然window.event已经不是一个标准的JavaScript对象了,所以如果有什么理所当然的事情在window.event上变得不对劲了也不要感到特别惊奇。

2、实际上,IE还是为所有事件公用一个Event对象。

我们直接使用下边的代码来说明问题。


 


<button id=”btn1″>Click1!</button>

<button id=”btn2″>Click2!</button>

<script type=”text/javascript”>

// <![CDATA[

var btn1EventObj = null;

document.getElementById('btn1').onclick = function () {

btn1EventObj = window.event;

alert(btn1EventObj.srcElement.id);

}

document.getElementById('btn2').onclick = function () {

alert(btn1EventObj === window.event);

alert(btn1EventObj.srcElement === window.event.srcElement);

alert(btn1EventObj.srcElement.id);

}

// ]]>

</script>

这次我们有了两个按钮,每个按钮都绑定了一个事件处理函数。我们先点击btn1按钮,该点击事件的Event对象的引用在btn1事件处理函数中被保存在了全局变量btn1EventObj中。然后我们试着用btn1EventObj变量来访问btn1的id属性,很好,弹出的消息框中的确显示的btn1。

接下来我们点击btn2按钮,在btn2的事件处理函数中我们做了一些测试。测试btn1EventObj === window.event时,不出所料,返回了false。看来window.event已经变成了另外一个Event对象的引用。然后我们再测试btn1EventObj.srcElement === window.event.srcElement,居然返回了true。等号左边的本来应该是按钮btn1,而右边应该是按钮btn2,现在它们居然相等了。然后我们再看看btn1EventObj.srcElement.id,结果它的值变成了btn2了。事情变得明了了,对象btn1EventObj与对象window.event之间的关系可以用下边的图来表示。


IE确实为每个事件创建了一个单独的Event对象,而window.event在事件处理函数的执行过程中也总是指向最新创建的Event对象。问题是每个Event对象的属性却共享的同一个属性值。在这个例子中,当按钮btn2被点击后,共享的属性值srcElement被更新为了按钮btn2。因此通过btn1EventObj.srcElement访问到的属性值也就被改变了。

3、被共享的Event对象属性值也只在事件处理函数的执行过程中才有效

在事件处理函数执行完毕后,并不仅仅是window.event被设置为null这么简单。可以看看如下代码:


 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

var eventObj = null;

document.getElementById('btn').onclick = function () {

eventObj = window.event;

setTimeout(function () {

alert(typeof eventObj.srcElement);

}, 1000);

}

// ]]>

</script>

猜猜最后弹出的消息框里的是什么?居然是unknown。微软自家的JScript文档里的有如下定义:

There are six possible values that typeof returns: “number,” “string,” “boolean,” “object,” “function,” and “undefined.”

所以这个unknown代表着事件处理函数执行后,srcElement属性所指向的属性值已经变成了未知的什么什么了。不仅仅是srcElement属性,诸如clientX、altKey等Event对象的其它属性在事件处理函数执行完毕后也是如此。夜深人静一个人写周报的我面对如此灵异的事实也不仅感觉到背脊有丝丝凉意。但是如果我们如果像下边的代码这样,在事件处理函数的执行过程中把属性值保存在其它的变量中,则被保存的属性值在事件处理函数执行后依然可以访问到。





 


<button id=”btn”>Click!</button>

<script type=”text/javascript”>

// <![CDATA[

var target = null;

document.getElementById('btn').onclick = function () {

target = window.event.srcElement;

setTimeout(function () {

alert(target.id);

}, 1000);

}

// ]]>

</script>

由此可以,srcElement属性的属性值在这段代码中表现出了值类型的性质,因为事件处理函数执行完毕后,即使IE把公用的属性值设置为了未知的什么什么后,保存在变量target中的属性值并没有受到印象。至于为什么通过Event对象的属性来访问属性值时属性值表现出了对象类型的性质,这个就只有IE的开发人员知道了。

以上是本周分享的有关window.event的灵异事件录。虽然到最后还是以“只有IE开发人员才知道”这种半途而废的文字收尾了,但是至少我们可以得出一些有用的结论来帮助我们在今后写代码的过程中回避类似问题:

1、在IE中,不要在事件处理函数的执行过程以外的地方来访问Event对象及其属性。

2、如果非要访问,请在事件处理函数的执行过程中用闭包等方式把Event对象的属性的属性值保存在其它变量中。

注:此文版权属于 邓楠乔

标签:window.event,对象,函数
0
投稿

猜你喜欢

  • Oracle SQL性能优化系列学习二

    2010-07-23 13:23:00
  • Dreamweaver技巧50问

    2008-10-16 14:00:00
  • ASP实现长文章自动分页的函数代码

    2008-10-10 17:09:00
  • 如何编写一个只在Web服务关闭时执行的程序?

    2009-11-08 19:03:00
  • 如何用我的国际域名做虚拟域名?

    2010-06-16 09:53:00
  • ASP实现表单中容量大的数据的提交方法

    2008-10-16 11:07:00
  • 有故事的网页设计——Flash网站奇妙之旅

    2011-01-20 19:58:00
  • IE中不可遍历的属性

    2010-01-19 13:47:00
  • 教你轻松学会SQL Server记录轮班的技巧

    2009-02-19 17:38:00
  • SQL Server 总结复习(一)

    2012-10-07 11:04:02
  • 非原型 不设计

    2010-01-21 12:51:00
  • 网站注册那些事儿

    2010-01-05 16:49:00
  • 从两个方面讲解SQL Server口令的脆弱性

    2009-01-08 13:40:00
  • 利用ASP输出excel文件一例

    2008-06-06 13:18:00
  • Oracle中大批量删除数据的方法

    2010-07-21 13:05:00
  • 网页设计进阶之六-- 守住那些不能丢的东西

    2008-06-12 13:06:00
  • asp中使用js的encodeURIComponent

    2008-10-13 09:19:00
  • 实现一个获取元素样式的函数getStyle

    2009-02-10 10:37:00
  • sqlserver中重复数据值只取一条的sql语句

    2012-06-06 19:46:31
  • 在SQL Server中使用CLR调用.NET方法

    2008-12-24 15:43:00
  • asp之家 网络编程 m.aspxhome.com