IE6,IE7全局变量的DID(Dissociative Identity Disorder译为:人格分裂症) 变量怪异的操作模式和存储

分类:Javascript| 发布:camnprbubuol| 查看: | 发表时间:2013/12/9

问题描述:

在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代码
<script>
window.x = 1
</script>

<script>
var x
alert(x)
</script>

按理应该输出“1”,但是在IE中输出“undefined”。 而将两段script合并在一起

 

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

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

Javascript代码
<script>
x = null
window.x = 1
</script>

<script>
var x
alert(x)
</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代码
x = 1
alert( delete x) // true
var y = 1
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代码
x = {}
// window.x
alert(x)
alert( delete x)
try {
alert(x)
} catch (e) {
alert(e.name + ":" + e.description)
}


以上代码可正常执行。但是如果把第二行的注释去掉,可以观察到在执行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代码
var size = 1000000

x = 0
// var x = 0
// window.x = 0

var code = new Array(size + 1).join( 'x;' )
// var code = new Array(size + 1).join('x=0;')
// var code = new Array(size + 1).join('window.x;')
// var code = new Array(size + 1).join('window.x=0;')

var f = new Function(code)
var start = new Date()
f()
alert( new Date - start)


结果如下:

Text代码
x x= window.x window.x=
x = 0 156 172 3187 3469
var x = 0 156 172 3281 3422
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的步骤:

①. JScript引擎调用DOM对象存取属性的方法,假设记做DOM::Window.get/setAttribute("x ")

②. DOM::Window.get/setAttribute()方法先询问JScript引擎是否已经有内建全局变量x,假设记做JScript::Global.hasProperty("x")

③. JScript::Global.hasProperty ("x")返回false

④.于是DOM::Window.get/setAttribute()方法存取expando属性,假设记做DOM::Window.get/setExpando("x")

⑤.将结果返回给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代码
<script>
window.x = 1
</script>

<script>
var x
alert(x)
</script>

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

 

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

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

 

Javascript代码
<script>
x = null
window.x = 1
</script>

<script>
var x
alert(x)
</script>

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


维基百科写道

多重人格的根治需要好几年的时间。

换到我们的语境下:

Dr. Hax 写道

IE的DID的根治可能还需要很多年时间(IE8已修复)。

 

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

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代码
<script>
x = ((window.x = 'expando' ), 0)
alert( 'x = ' + x)
//window.x

try {
alert( 'delete x -> ' + ( delete x))
} catch (e) {
alert(e.name + ': ' + e.description)
}
try {
alert( 'window.x = ' + window.x)
} catch (e) {
alert(e.name + ': ' + e.description)
}
try {
alert( 'x = ' + x)
} catch (e) {
alert(e.name + ': ' + e.description)
}
</script>
<script>
alert( 'x = ' + x)
var x = 'var'
</script>


在IE下输出什么?恢复被注释掉的第三行代码后呢? 答案请自己动手试验。 PS.钻研DID多年的Dr. Hax还透露,实际上IE全局变量的DID不仅有两个变量格,实际上还有第三个变量格!你猜到了吗?欢迎留言竞猜。

via

365据说看到好文章不转的人,服务器容易宕机
原创文章如转载,请注明:转载自郑州网建-前端开发 http://camnpr.com/
本文链接:http://camnpr.com/archives/ie6-ie7-dissociative-identity-disorder.html