原文地址:http://hax.iteye.com/blog/349569

最近,小麦提出了一个疑惑:

小麦 写道
最后介绍一个我也搞不明白的问题:

在HTML文档里写上这段代码:

<script type="text/javascript"> 
    window['a'] = 'Hi'; 
</script> 
<script type="text/javascript" src="out.js"></script> 
<script type="text/javascript"> 
    alert(a); 
</script>

然后在out.js里写上这句:

if(false) { 
     var a = 'Hello'; 
}

然后用FF和IE6分别运行,看看你得到什么。

在FF里会弹出“Hi”,但是在IE6中,会得到“undefined”。

很神奇吧?按语法,无论如何,a都不可能是undefined。但是IE6里就会。

如果把两个语句都写在同一个文件里,就不会有这个情况。 
如果把out.js里改成window.a,或者把前一个改成var a,也不会有这个情况。 
如果把out.js里的var a移到if语句之外,或是把if的条件改为true,也不会有这个情况。

这里刨去其实无关的 if 语句,可以把问题简化成这样的形式:

Javascript代码  
  1. <script>
  2. window.x = 1
  3. </script>
  4. <script>
  5. var x
  6. alert(x)
  7. </script>

按理应该输出“1”,但是在IE中输出“undefined”。

而将两段script合并在一起

Javascript代码  
  1. <script>
  2. window.x = 1
  3. var x
  4. alert(x)
  5. </script>

或者先加上一句“x = xxx”的赋值语句

Javascript代码  
  1. <script>
  2. x = null
  3. window.x = 1
  4. </script>
  5. <script>
  6. var x
  7. alert(x)
  8. </script>

输出结果就是正常的“1”。

这神奇的现象到底是为什么涅?

Dr. Hax告诉你:这是因为IE下的全局变量存在DID

Dr. Hax 写道
所谓DID,就是Dissociative identity disorder,译成中文就是“解离性自我认同紊乱”,放到人的身上,就是解離性人格疾患,即俗称“多重人格”、“人格分裂”是也。 

我们知道创建global变量有三种方式,一种是直接用名字:

x = 1 
alert(x)

一种是用var声明:

var x = 1 
alert(x)

除了明确进行了变量声明外,主要区别是,var x声明所创建的x是不能被delete的。

Javascript代码  
  1. x = 1
  2. alert(delete x) // true
  3. var y = 1
  4. alert(delete y) // false

还有一种呢,就是通过global对象的属性,即window.x或window["x"]的赋值来创建:

window.x = 1 
alert(window.x)

在创建完成之后,存取全局变量就是两种方式,一种是直接通过名字,如:

if(x == 1) x = 2

另一种则通过global对象的属性来存取,如:

if (window["x"] == 1) window.x = 2

按照BE大神的设计,全局变量的三种创建方式本应是三位一体的,两种存取方式也应是等同的,但是在M$IE中,其window.x的实现导致了DID问题——根据Dr. Hax的观察,在IE中全局变量分裂出了至少两种变量格

我们来看看临床症状

维基百科 写道
多重人格患者的每一個人格都是穩定、發展完整、擁有各別思考模式和記憶的。……分裂出的人格之間知道彼此的存在,也有一些情況,人格之間並沒有察覺彼此的存在,這會導致嚴重的「遺失時間」現象。 

翻译到我们的语境则是:

Dr. Hax 写道
IE下的每一种全局变量格都是稳定、开发完整、拥有各自操作模式和存储的。……分裂的变量格之间知道彼此的存在(因此,通常来说,无论用哪种方式存取,结果是一致的),但也有一些情况,它们之间没有察觉彼此的存在,这会导致严重的“遗失引用”现象。
Javascript代码  
  1. x = {}
  2. // window.x
  3. alert(x)
  4. alert(delete x)
  5. try {
  6. alert(x)
  7. } catch(e) {
  8. alert(e.name + ":" + e.description)
  9. }

以上代码可正常执行。但是如果把第二行的注释去掉,可以观察到在执行 alert(x) 时报出一个“Out of memory”的错误!其后你也将不能对x或者window.x做任何事情。 
这短短的代码怎么能out of memory呢——显然这里产生了一个空指针(“遗失引用”)错误!

让我们进行成因分析。

维基百科 写道
多重人格的產生與童年創傷有密切相關,尤其是性侵害。患者的男女比(1:9)可以作為佐證,這或許是女孩比男孩易受到性侵害的緣故。當受到難以應付的衝擊時,患者以「放空」的方式,以達到「這件事不是發生在我身上」的感覺,這對長期受到嚴重傷害的人(如近親相姦)來說,或許是必要的。 

转换一下语境:

Dr. Hax 写道
多重变量格的产生与最初设计的bug有密切相关,尤其是对接的bug。开源软件和私有软件出现对接问题的比例(0:1?)可以作为佐证,这或许是私有软件(因不遵循标准)比开源软件更多受到对接问题困扰的缘故。当受到难以应付的调用时,问题软件以“out of memory”的方式,以达到“这件事不在我的记忆体中”的感觉,这对长期受到严重自大症困扰的公司(如开发者)来说,或许是必然的。

为什么会这样?大体是因为JScript引擎的设计与IE DOM对接的缺陷所导致。

我们也可以从另一个方面来观察这个问题——我们来做一个性能力测试:

Javascript代码  
  1. var size = 1000000
  2. x = 0
  3. // var x = 0
  4. // window.x = 0
  5. var code = new Array(size + 1).join('x;')
  6. // var code = new Array(size + 1).join('x=0;')
  7. // var code = new Array(size + 1).join('window.x;')
  8. // var code = new Array(size + 1).join('window.x=0;')
  9. var f = new Function(code)
  10. var start = new Date()
  11. f()
  12. alert(new Date - start)

结果如下:

Text代码  
  1. x   x=  window.x  window.x=
  2. x = 0          156  172   3187      3469
  3. var x = 0      156  172   3281      3422
  4. window.x = 0  1516 1906   3297      3422

这个性能力测试告诉我们:

一、无论以何种方式创建全局变量,访问window.x总是比直接访问x要慢许多。访问window.x慢,这还是容易理解的。因为window.x实际上是DOM调用,可理解成被转换为node.getAttribute('x')。而直接访问x,则很可能做了不同的处理(比如也许省略了跨域安全检查)。

二、“x = 0”和“var x = 0”所创建的全局变量,存取性能上没有什么区别,而“window.x = 0”则差很多。这说明在创建全局变量时,前者和后者的存储方式很可能是完全不同的。

以上这两点,都完全符合Dr. Hax在临床症状中所描述的“……拥有各自操作模式存储”。

由于无法进行病理解剖(没有IE和JScript引擎的源码),我们无法搞清楚所有细节,但是我们可以推断出个大概:

1. JScript引擎当然可以独立使用,而不依赖于DOM。在这样一种情况下,它本身就要处理全局变量的问题——譬如所有built-in的那些方法和对象。当引擎被嵌入到IE中时,就将JScript引擎的Global对象和DOM的window对象连接起来,而window对象,如同所有DOM对象一样,能够以expando属性方式保存额外的属性。这样就有了两个变量格,一个是基于JScript引擎内建的全局变量机制,一个是基于DOM的expando机制。

2. 对于x的存取和对于window.x的存取,前者是对于x这个引用的存取,而后者是对于window这个引用上进行x属性的存取。注意到这一区别,有助于理解下面的分析。

3. 以“x = xxx”方式新建全局变量时(前提是之前不存在任何形式的x引用),引擎并不会将x作为window的expando属性保存,而是按照JScript引擎的内建方式保存,并建立指向内部存储的x引用。内建方式是很高效的,比expando属性存取要快20倍以上。

上述判断,由DOM中的 document.expando 特性也可以佐证。当 document.expando=false 之后,你将不能使用 window.x = xxx 的方式新建全局变量,但是仍然可以用 x = xxx / var x = xxx 。

4. 在访问window.x时,如果已经有按照内建方式建立的x,则进行window.x到x的绑定,即产生一个指针指向内建方式创建的x,然后就凭借此指针来进行存取操作。然而在这里IE的开发者忘记了x引用可能被delete的情况,在x被delete之后,指针就失效了,从而导致之前的“Out of memory”错误。

5. 在对window.x进行赋值时,如果不存在按照内建方式保存的x,则会以window的expando属性方式保存(如果允许expando的话)。

6. 而在访问x时,如果没有按照内建方式建立的x引用,则检查引擎所连接的window对象上是否有x,即window上是否有expando。如果有,就建立x引用并指向window上的expando。如果没有,就扔出TypeError(按照标准其实应该扔出ReferenceError)。

7. 那么为什么在以expando方式建立变量后,存取window.x比存取x慢呢? 
老实说,这个问题很难回答,只能大胆猜想一下:

存取window.x的步骤: 
a. JScript引擎调用DOM对象存取属性的方法,假设记做 DOM::Window.get/setAttribute("x") 
b. DOM::Window.get/setAttribute()方法先询问JScript引擎是否已经有内建全局变量x,假设记做 JScript::Global.hasProperty("x") 
c. JScript::Global.hasProperty("x")返回false 
d. 于是DOM::Window.get/setAttribute()方法存取expando属性,假设记做 DOM::Window.get/setExpando("x") 
e. 将结果返回给JScript引擎

存取x的步骤: 
a. JScript引擎查找是否存在x引用 
b. JScript引擎根据x引用调用DOM::Window.get/setExpando("x") 
c. DOM::Window.get/setExpando("x")返回结果给JScript引擎

根据上述猜想,存取window.x,是JScript调用DOM,DOM再回调JScript;而存取x,则只有JScript调用DOM。这就是性能差大概一倍的原因。

好了,绕了一大圈之后,我们来解释一下最初的代码为什么会有这样的结果。

Javascript代码  
  1. <script>
  2. window.x = 1
  3. </script>
  4. <script>
  5. var x
  6. alert(x)
  7. </script>

这段代码会先以expando方式创建x。而后var x声明所进行的初始化操作,并不依赖于DOM,所以不会检测是否存在expando。道理上说应该检测,但是由于罹患DID,恰好此处JScript引擎并没有检测expando,而只会检测内建存储。并不存在内建的x,所以x引用就被重新初始化了,而且这次是以内建方式建立的。

而合并在一起后

Javascript代码  
  1. <script>
  2. window.x = 1
  3. var x
  4. alert(x)
  5. </script>

var x声明会被提前,所以会以内建方式创建。执行 window.x = 1 时,已经有了内建的x,所以会建立到内建x的绑定,并对其赋值,而不会以expando方式保存。

先加上一句 x = xxx 的赋值语句

Javascript代码  
  1. <script>
  2. x = null
  3. window.x = 1
  4. </script>
  5. <script>
  6. var x
  7. alert(x)
  8. </script>

也会以内建方式创建x。这样之后的var x就不会重新初始化了。

最后说说治疗方案

维基百科 写道
多重人格的根治需要好幾年的時間。 

换到我们的语境下:

Dr. Hax 写道
IE的DID的根治可能还需要很多年时间(IE9?)。

当然,我们有些简单的应对之道。

1. 不用全局变量,不折腾window对象。

从编程上说,使用全局变量不是一个好习惯。从JS效率而言,全局变量效率比局部变量要低许多。再加上IE独有的DID症,咱能不用就不用吧!

2. 如果一定要用全局变量,坚持直来直去,不折腾window对象。

也就是只用 var x 的方式。效率最高,也避免了DID症的可能发作。

3. 宁可折腾DOM元素,也不折腾window对象。

动态变量名,即window[name]之类的,可用一个 var container={name:value} 来替代,或者最不济的情形,你存到一个DOM元素上也好!

一样expando,DOM元素相比window,有个重要优点是有clearAttributes()方法可以清空所有属性(相当于delete操作),或者更快捷的方法是用parent.innerHTML或node.outerHTML直接清空。

或许你认为清空expando不重要,大谬!不信,你写个脚本给DOM元素上加10万个expando属性,然后按一次刷新看看。

附:DID疑难案例

Javascript代码  
  1. <script>
  2. x = ((window.x = 'expando'), 0)
  3. alert('x = ' + x)
  4. //window.x
  5. try {
  6. alert('delete x -> ' + (delete x))
  7. } catch(e) {
  8. alert(e.name + ': ' + e.description)
  9. }
  10. try {
  11. alert('window.x = ' + window.x)
  12. } catch(e) {
  13. alert(e.name + ': ' + e.description)
  14. }
  15. try {
  16. alert('x = ' + x)
  17. } catch(e) {
  18. alert(e.name + ': ' + e.description)
  19. }
  20. </script>
  21. <script>
  22. alert('x = ' + x)
  23. var x = 'var'
  24. </script>

在IE下输出什么?恢复被注释掉的第三行代码后呢?

答案请自己动手试验。

PS. 钻研DID多年的Dr. Hax还透露,实际上IE全局变量的DID不仅有两个变量格,实际上还有第三个变量格!你猜到了吗?欢迎留言竞猜。

评论
20 楼 libmw 2011-11-04  
libmw 写道
呼呼。。ie8治好了

刚试了下,悲剧得还没治好

19 楼 libmw 2011-11-04  
呼呼。。ie8治好了

18 楼 bmcsy 2010-06-17  
虽然只是现在才看到,頩已经看完了,在概叹——我的神呀之余,真让人收获不少~~也知道了平时不晓得的。

17 楼 felsenlee 2009-12-22  
cuixiping 写道
分析的很全面和深入。

ie中的元素id可以直接当变量名一样使用,但是这个id变量不能被赋值.
<body id='body'>
<script type="text/javascript">
body=1;
</script>
</body>
这样就会脚本错误提示“对象不支持此属性或方法”。

所以,是定义变量,最好老老实实的var.上例中使用var body=1才可以。

“ body=1;”是个什么操作呢?比较奇怪!这肯定会报错啊,赋值也不是直接给对象赋吧。

16 楼 cuixiping 2009-12-20  
分析的很全面和深入。

我想,把window.x和var x混用本身就是个很坏的编程习惯,没有var就直接x=xxx也是很坏的编程习惯。

ie中的元素id可以直接当变量名一样使用,但是这个id变量不能被赋值. 
<body id='body'> 
<script type="text/javascript"> 
body=1; 
</script> 
</body> 
这样就会脚本错误提示“对象不支持此属性或方法”。

所以,是定义变量,最好老老实实的var.上例中使用var body=1才可以。

15 楼 s79 2009-11-29  
似乎这个问题也能在这找到解释(IE7以后直接提供了XMLHttpRequest)。 
if(typeof XMLHttpRequest=="undefined"){ 
alert(0); 
var XMLHttpRequest={}; //省略。 

14 楼 cuckoosnest 2009-11-15  
有没有发现用chrome运行那段性能力测试代码非常慢,5,6秒的级别,opera非常快六七十ms级别。 大概是什么原因?

13 楼 achun 2009-08-11  
Dr.Hax
哈哈,太好玩了,这个可以找个声优做成有声版,小说?广播剧?
油菜呀

12 楼 Army 2009-03-26  
貌似我一猜就猜中了,别人连机会都没有,没留啥悬念下来,可惜了。

11 楼 hax 2009-03-26  
Army 写道

我还是选C,看看《ajax实战》吧~另外,签名是签Dr. hax呢,还是签真实姓名呢?我拿去炫耀一下去~

站内留言地址,我邮寄给你,呵呵。

10 楼 Army 2009-03-26  
hax 写道

Army 写道 
猜一下,难道是ie里对dom节点的id直接引用?中!发奖品了。。。奖品选项如下:A. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进午餐B. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进晚餐C. 获得由Dr. Hax签名的《Ajax实战:实例详解》一本D. 获得没有Dr. Hax签名的《Ajax实战:实例详解》一本 

吃惊,我居然猜对了!

我住在上海南站附近,接近闵行区,张江的话太远了……

我还是选C,看看《ajax实战》吧~另外,签名是签Dr. hax呢,还是签真实姓名呢?我拿去炫耀一下去~

9 楼 hax 2009-03-26  
Army 写道

猜一下,难道是ie里对dom节点的id直接引用?

中!

发奖品了。。。奖品选项如下:

A. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进午餐
B. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进晚餐
C. 获得由Dr. Hax签名的《Ajax实战:实例详解》一本
D. 获得没有Dr. Hax签名的《Ajax实战:实例详解》一本

8 楼 Army 2009-03-25  
猜一下,难道是ie里对dom节点的id直接引用?

7 楼 Army 2009-03-25  
前阵子在开发JAte的时候就碰到了,如果var一个object和swf的名字相同,js取得这个swf对象的时候就会取到var的那个变量上,得改个名字才行。 
原理原来在这里……

6 楼 hax 2009-03-24  
beijing.josh 写道

IE的分裂症还包括:window.clearInterval能重写,clearInterval不能重写.所有以内建方式建立的window方法的引用都不能重写和删除.是否这是第三个变量格?

不是的。要构成“变量格”必须满足Dr. Hax定义的条件:拥有各自操作模式和存储。
其中的关键是一个全局变量引用的目标到底是指向哪里。第三种变量格其实(在早期脚本里)很常见的。

5 楼 hax 2009-03-24  
麦田的颜色 写道

sorry,Hax,好久没联系。

我不知道我问你要过MSN没?加我一下吧。我是小麦。

加过了,不过我99%时候不在线。。。

4 楼 beijing.josh 2009-03-23  
IE的分裂症还包括:window.clearInterval能重写,clearInterval不能重写.
所有以内建方式建立的window方法的引用都不能重写和删除.
是否这是第三个变量格?

3 楼 麦田的颜色 2009-03-23  
sorry,Hax,好久没联系。

我不知道我问你要过MSN没?加我一下吧。我是小麦。

2 楼 lifesinger 2009-03-16  
赞,Dr. Hax 太有意思啦
开诊所吧

1 楼 NOWGOO 2009-03-16  
非常棒的文章!

IE全局变量的Dissociative Identity Disorder(人格分裂症)相关推荐

  1. did模型(did模型适用范围)

    心理学DID什么意思 分离性身份识别障碍(Dissociative Identity Disorder,DID),以前被称为多重人格障碍(Multiple Personality Disorder,M ...

  2. 元宇宙的“42条共识”

    来源: 量子学派 *以下内容.图片综合自 <图说元宇宙><设计元宇宙>系列 01 元宇宙不是一天建成的 罗马不是一天建成的,元宇宙也一样. 人类从未像今天这样,可以自己成为&q ...

  3. 网络安全先驱传奇大佬自杀了,他的一生足够拍成一部电影

    网络安全先驱传奇大佬自杀了,他的一生足够拍成一部电影 据西班牙报纸 El Pais 报道,网络安全先驱 John McAfee 在西班牙的监狱中死亡,享年 75 岁. McAfee 的律师称,John ...

  4. 网络安全先驱传奇自杀了,他的一生足够拍成一部电影

    网络安全先驱传奇大佬自杀了,他的一生足够拍成一部电影 据西班牙报纸 El Pais 报道,网络安全先驱 John McAfee 在西班牙的监狱中死亡,享年 75 岁. McAfee 的律师称,John ...

  5. 张峥、小白谈GPT与人工智能:可能是好事,也可能不是

    张峥.小白(章静绘) 最近几个月,以ChatGPT为代表的聊天机器人引起全世界的广泛关注.GPT是如何工作的?它会有自我意识吗?人工智能将对我们的社会.文化.知识系统造成何种冲击和重构?奇点到了吗?未 ...

  6. 元宇宙的“42条共识” ,全网阅读量超1000万!

    文末送书5本 " 42 出自科幻圣经<银河系漫游指南> 一台名叫 Deep Thought 的超级电脑 经过750万年计算 得出以下答案 ▼ 生命.宇宙与一切的终极答案是 &qu ...

  7. 实话实说 —— 心理模型vs实现模型

    三金:欢迎各位来到这一期的CDC实话实说节目.我是主持人三金,不是三精牌葡萄糖酸钙那个精,是金子总会发光的那个金.今天我们请到了一对传奇人物"实现模型"和"心理模型&qu ...

  8. 这个传奇大佬,自杀了!

    0 自杀 据西班牙报纸 El Pais 报道,网络安全先驱 John McAfee 于周三在西班牙的监狱中死亡,享年 75 岁. McAfee 的律师称,John McAfee 在九个月的监禁中因绝望 ...

  9. 为什么股票投资是世界上最难成功的行业

    我在二级市场摸滚打爬的时间已经十多年,接触过的投资者起码超过1000个,但真正成功实现财务自由的不足10人,即成功率不到1%.这里把那些证券服务行业的人剔除,例如投行精英.券商老总.他们并不是靠炒股赚 ...

最新文章

  1. NCSDK make install: Error in line 170
  2. goahead如何使用cgi服务_QQ如何设置使用代理服务器?
  3. 上海职称英语计算机取消,小编简析2017年职称英语考试是否取消
  4. 【代码笔记】iOS-对UIView进行截图
  5. css display: inline-block 去间隙
  6. php限制ip访问次数 并发_nginx限制ip请求次数 以及并发次数
  7. 思科修复NSA报告的Nexus 交换机DoS漏洞及其它
  8. 经典排序算法(十二)--地精排序Gnome Sort
  9. 蓝桥杯_算法训练_审美课
  10. php查netstat,Netstat命令详解
  11. typedef用法总结
  12. 提高免疫力吃什么 多吃奶制品
  13. 置换密码c语言,替代密码和置换密码的C语言实现
  14. php 微信定位源码_微信活码模块源码 - WEB源码|PHP源码|源代码 - 源码中国
  15. php.c drcom,成功 将校园客户端drcom搞进openwrt
  16. Codeforces Round #548 (Div. 2) C. Edgy Trees(dfs || 并查集)
  17. java8新特性(2)-Lambda表达式
  18. 基于python和TensorFlow的电影推荐系统
  19. html验证座机号码_JS校验手机号 座机 邮箱 微信号详解
  20. 机器学习之朴素贝叶斯 1

热门文章

  1. 鸿蒙 手机 发布时间 2021,2021下半年新机发布时间表_2021下半年新机发布日期
  2. 《画解数据结构》九个动图,画解栈
  3. ubuntu 安装网络打印机(hp laserjet 3055 pcl5)
  4. Nginx 安装 使用 卸载
  5. 群表示论之Q8四元数群的特征标表和二阶不可约复表示
  6. 学习 HTML+CSS 这一篇就够了
  7. 根据输入的正整数y所代表的年份,计算输出该年份是否为闰年 闰年的判断标准:
  8. css:ios底部安全距离适配
  9. 程序员福利:各大平台免费接口
  10. 微头条全自动循环发文软件