IE全局变量的Dissociative Identity Disorder(人格分裂症)
原文地址: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 语句,可以把问题简化成这样的形式:
- <script>
- window.x = 1
- </script>
- <script>
- var x
- alert(x)
- </script>
按理应该输出“1”,但是在IE中输出“undefined”。
而将两段script合并在一起
- <script>
- window.x = 1
- var x
- alert(x)
- </script>
或者先加上一句“x = xxx”的赋值语句
- <script>
- x = null
- window.x = 1
- </script>
- <script>
- var x
- alert(x)
- </script>
输出结果就是正常的“1”。
这神奇的现象到底是为什么涅?
Dr. Hax告诉你:这是因为IE下的全局变量存在DID。
我们知道创建global变量有三种方式,一种是直接用名字:
x = 1
alert(x)
一种是用var声明:
var x = 1
alert(x)
除了明确进行了变量声明外,主要区别是,var x声明所创建的x是不能被delete的。
- 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中全局变量分裂出了至少两种变量格。
我们来看看临床症状。
翻译到我们的语境则是:
- 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呢——显然这里产生了一个空指针(“遗失引用”)错误!
让我们进行成因分析。
转换一下语境:
为什么会这样?大体是因为JScript引擎的设计与IE DOM对接的缺陷所导致。
我们也可以从另一个方面来观察这个问题——我们来做一个性能力测试:
- 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)
结果如下:
- 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的步骤:
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。这就是性能差大概一倍的原因。
好了,绕了一大圈之后,我们来解释一下最初的代码为什么会有这样的结果。
- <script>
- window.x = 1
- </script>
- <script>
- var x
- alert(x)
- </script>
这段代码会先以expando方式创建x。而后var x声明所进行的初始化操作,并不依赖于DOM,所以不会检测是否存在expando。道理上说应该检测,但是由于罹患DID,恰好此处JScript引擎并没有检测expando,而只会检测内建存储。并不存在内建的x,所以x引用就被重新初始化了,而且这次是以内建方式建立的。
而合并在一起后
- <script>
- window.x = 1
- var x
- alert(x)
- </script>
var x声明会被提前,所以会以内建方式创建。执行 window.x = 1 时,已经有了内建的x,所以会建立到内建x的绑定,并对其赋值,而不会以expando方式保存。
先加上一句 x = xxx 的赋值语句
- <script>
- x = null
- window.x = 1
- </script>
- <script>
- var x
- alert(x)
- </script>
也会以内建方式创建x。这样之后的var x就不会重新初始化了。
最后说说治疗方案。
换到我们的语境下:
当然,我们有些简单的应对之道。
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疑难案例
- <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不仅有两个变量格,实际上还有第三个变量格!你猜到了吗?欢迎留言竞猜。
评论
刚试了下,悲剧得还没治好
ie中的元素id可以直接当变量名一样使用,但是这个id变量不能被赋值.
<body id='body'>
<script type="text/javascript">
body=1;
</script>
</body>
这样就会脚本错误提示“对象不支持此属性或方法”。
所以,是定义变量,最好老老实实的var.上例中使用var body=1才可以。
“ body=1;”是个什么操作呢?比较奇怪!这肯定会报错啊,赋值也不是直接给对象赋吧。
我想,把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才可以。
if(typeof XMLHttpRequest=="undefined"){
alert(0);
var XMLHttpRequest={}; //省略。
}
哈哈,太好玩了,这个可以找个声优做成有声版,小说?广播剧?
油菜呀
我还是选C,看看《ajax实战》吧~另外,签名是签Dr. 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呢,还是签真实姓名呢?我拿去炫耀一下去~
猜一下,难道是ie里对dom节点的id直接引用?
中!
发奖品了。。。奖品选项如下:
A. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进午餐
B. 与Dr. Hax在张江地铁站周边方圆5公里以内的食堂里共进晚餐
C. 获得由Dr. Hax签名的《Ajax实战:实例详解》一本
D. 获得没有Dr. Hax签名的《Ajax实战:实例详解》一本
原理原来在这里……
IE的分裂症还包括:window.clearInterval能重写,clearInterval不能重写.所有以内建方式建立的window方法的引用都不能重写和删除.是否这是第三个变量格?
不是的。要构成“变量格”必须满足Dr. Hax定义的条件:拥有各自操作模式和存储。
其中的关键是一个全局变量引用的目标到底是指向哪里。第三种变量格其实(在早期脚本里)很常见的。
sorry,Hax,好久没联系。
我不知道我问你要过MSN没?加我一下吧。我是小麦。
加过了,不过我99%时候不在线。。。
所有以内建方式建立的window方法的引用都不能重写和删除.
是否这是第三个变量格?
我不知道我问你要过MSN没?加我一下吧。我是小麦。
开诊所吧
IE全局变量的Dissociative Identity Disorder(人格分裂症)相关推荐
- did模型(did模型适用范围)
心理学DID什么意思 分离性身份识别障碍(Dissociative Identity Disorder,DID),以前被称为多重人格障碍(Multiple Personality Disorder,M ...
- 元宇宙的“42条共识”
来源: 量子学派 *以下内容.图片综合自 <图说元宇宙><设计元宇宙>系列 01 元宇宙不是一天建成的 罗马不是一天建成的,元宇宙也一样. 人类从未像今天这样,可以自己成为&q ...
- 网络安全先驱传奇大佬自杀了,他的一生足够拍成一部电影
网络安全先驱传奇大佬自杀了,他的一生足够拍成一部电影 据西班牙报纸 El Pais 报道,网络安全先驱 John McAfee 在西班牙的监狱中死亡,享年 75 岁. McAfee 的律师称,John ...
- 网络安全先驱传奇自杀了,他的一生足够拍成一部电影
网络安全先驱传奇大佬自杀了,他的一生足够拍成一部电影 据西班牙报纸 El Pais 报道,网络安全先驱 John McAfee 在西班牙的监狱中死亡,享年 75 岁. McAfee 的律师称,John ...
- 张峥、小白谈GPT与人工智能:可能是好事,也可能不是
张峥.小白(章静绘) 最近几个月,以ChatGPT为代表的聊天机器人引起全世界的广泛关注.GPT是如何工作的?它会有自我意识吗?人工智能将对我们的社会.文化.知识系统造成何种冲击和重构?奇点到了吗?未 ...
- 元宇宙的“42条共识” ,全网阅读量超1000万!
文末送书5本 " 42 出自科幻圣经<银河系漫游指南> 一台名叫 Deep Thought 的超级电脑 经过750万年计算 得出以下答案 ▼ 生命.宇宙与一切的终极答案是 &qu ...
- 实话实说 —— 心理模型vs实现模型
三金:欢迎各位来到这一期的CDC实话实说节目.我是主持人三金,不是三精牌葡萄糖酸钙那个精,是金子总会发光的那个金.今天我们请到了一对传奇人物"实现模型"和"心理模型&qu ...
- 这个传奇大佬,自杀了!
0 自杀 据西班牙报纸 El Pais 报道,网络安全先驱 John McAfee 于周三在西班牙的监狱中死亡,享年 75 岁. McAfee 的律师称,John McAfee 在九个月的监禁中因绝望 ...
- 为什么股票投资是世界上最难成功的行业
我在二级市场摸滚打爬的时间已经十多年,接触过的投资者起码超过1000个,但真正成功实现财务自由的不足10人,即成功率不到1%.这里把那些证券服务行业的人剔除,例如投行精英.券商老总.他们并不是靠炒股赚 ...
最新文章
- NCSDK make install: Error in line 170
- goahead如何使用cgi服务_QQ如何设置使用代理服务器?
- 上海职称英语计算机取消,小编简析2017年职称英语考试是否取消
- 【代码笔记】iOS-对UIView进行截图
- css display: inline-block 去间隙
- php限制ip访问次数 并发_nginx限制ip请求次数 以及并发次数
- 思科修复NSA报告的Nexus 交换机DoS漏洞及其它
- 经典排序算法(十二)--地精排序Gnome Sort
- 蓝桥杯_算法训练_审美课
- php查netstat,Netstat命令详解
- typedef用法总结
- 提高免疫力吃什么 多吃奶制品
- 置换密码c语言,替代密码和置换密码的C语言实现
- php 微信定位源码_微信活码模块源码 - WEB源码|PHP源码|源代码 - 源码中国
- php.c drcom,成功 将校园客户端drcom搞进openwrt
- Codeforces Round #548 (Div. 2) C. Edgy Trees(dfs || 并查集)
- java8新特性(2)-Lambda表达式
- 基于python和TensorFlow的电影推荐系统
- html验证座机号码_JS校验手机号 座机 邮箱 微信号详解
- 机器学习之朴素贝叶斯 1