奇怪的 IE8 内部 [[ class ]] 属性行为
Posted
技术标签:
【中文标题】奇怪的 IE8 内部 [[ class ]] 属性行为【英文标题】:Weird IE8 internal [[ class ]] attribute behavior 【发布时间】:2012-04-09 22:52:30 【问题描述】:我最近在读取和比较某些[[Class]]
属性的值时遇到了一些问题(我目前还不知道 9)。实际上,它仅适用于 localStorage
对象。
我正在使用这样的方法
var ToStr = Object.prototype.toString;
Object.type = function _type( obj )
var res = ToStr.call( obj ).split( ' ' )[ 1 ].replace( ']', '' );
if( obj === window )
res = 'Window';
else if( res === 'Window' || res === 'Global' )
res = 'Undefined';
else if( res.indexOf( 'html' ) === 0 )
res = 'Node';
return ( res );
;
此方法将返回此值,例如:
var foo = ,
bar = [ ],
num = 52,
win = window;
Object.type( foo ) === 'Object'; // true
Object.type( bar ) === 'Array'; // true
Object.type( num ) === 'Number'; // true
Object.type( win ) === 'Window'; // true
这当然有效,在我知道的所有浏览器中,只需检查对象本身的 [[Class]]
属性即可。现在,我在 localStorage
对象上调用此方法
Object.type( win.localStorage ) === 'Storage' // true (not in IE8)
IE8 在这里只返回Object
。但是,这不是实际问题,当您尝试将localStorage
对象与window
对象进行比较时,就会出现问题。如您所见,我正在检查传入的参数是否是当前的window
对象
if( obj === window )
如果 obj
现在是 window.localStorage
对象,这将导致错误
"Class does not support automation"
只有当您尝试将localStorage
与window
进行比较时才会发生这种情况,您可以将它与其他任何东西进行比较而不会遇到任何问题。这只是另一个错误还是我可以以某种方式解决这个问题?
我想基本上我的问题是:
您如何知道在 IE8(也可能是 IE9)中处理的是localStorage
对象?
我要做的最后一件事是用try-catch
对整个方法进行内部包装,因为它经常被调用。
这里完全让我感到困惑:当你在 IE8 的控制台中执行 console.log( obj )
时,它会返回 [object Storage]
(很好!)但如果你调用 Object.prototype.toString.call( obj )
,它会返回 [object Object]
。 typeof obj
也是如此,将返回 object
。
第二个问题:
IE8的console
如何打印出正确的[[Class]]
?
【问题讨论】:
仅供参考:我检查过,IE9 可以正常工作,并且没有显示任何问题。 只是基于我记忆中一些随机事物的疯狂猜测:如果您针对window.window
而不是仅window
进行测试有什么不同吗? (或者可能是window.self
?)
@Pointy:刚刚试过,在 IE8 中出现同样的错误(如果这样我会永远 ragequit IE)
好的。好吧,我刚刚回忆起某些版本的 IE 中的 window
引用与 window.window
不同。您可以动态地创建函数并在创建它的函数中进行测试,而不是总是在 try ... catch
中编码,从而得到一个带有 try ... catch
的 IE8 版本,否则是一个普通版本。
@Pointy:是的,我可以做到。反正我不喜欢。如果它是localStorage
对象,我仍然希望有一些神奇的属性或任何可以在代码中告诉你的东西。 console
可以做到...
【参考方案1】:
我找到了一种使用隐式 toString()
操作来解决 IE8 行为的方法,ECMAScript 规范解释了为什么该解决方法是有意义的。隐含的toString()
是这样的:
"" + window.localStorage
这隐含地强制调用对象的内部 toString()
方法,在 IE 中,这将返回您想要的所需形式 [object Storage]
并且您可以让您的代码在没有特殊大小写 window.localStorage
的情况下工作。
所以,我一直在寻找一种风险最小的方法来将其合并到您现有的代码中。选择的方法是以与获取类型相同的方式获取类型,并且当且仅当它返回通用“对象”类型时,然后查看是否有更好的名称可用于新方法。因此,所有过去正常工作的东西都将继续以它们的方式工作,我们可能会为一些过去返回通用“对象”名称的对象(如window.localStorage
)找到更好的名称。另一个变化是我对我们可能从"" + obj
构造获得的确切返回类型不太自信,所以我想要一种不会对意外数据抛出错误的解析方法,所以我从拆分切换到正则表达式/replace 您正在使用的方法。正则表达式还强制要求它实际上也是 [object Type]
格式,这似乎是可取的。
然后,为了防止比较 localStorage === window
并出现错误的奇怪问题,您可以添加一个类型检查(鸭子类型),非窗口类对象不会通过,这将过滤掉 localStorage
问题和具有相同问题的任何其他对象。在这种特殊情况下,我确保对象的类型是"object"
,并且它有一个名为setInterval
的属性。我们可以选择window
对象的任何众所周知的、受良好支持的属性,该属性不太可能出现在任何其他对象上。在这种情况下,我使用setInterval
,因为这与 jQuery 在想知道对象是否是窗口时使用的测试相同。请注意,我还将代码更改为根本不与window
进行显式比较,因为可以有多个window
对象(框架、iframe、弹出窗口等......)所以这样,它将返回“窗口”对于任何窗口对象。
代码如下:
Object.type = function _type( obj )
function parseType(str)
var split = str.split(" ");
if (split.length > 1)
return(split[1].slice(0, -1));
return("");
var res = parseType(Object.prototype.toString.call(obj));
// if type is generic, see if we can get a better name
if (res === "Object")
res = parseType("" + obj);
if (!res)
res = "Object";
// protect against errors when comparing some objects vs. the window object
if(typeof obj === "object" && "setInterval" in obj)
res = 'Window';
else if( res === 'Window' || res === 'Global' )
res = 'Undefined';
else if( res.indexOf( 'HTML' ) === 0 )
res = 'Node';
return ( res );
;
在此处查看包含各种测试用例的演示:http://jsfiddle.net/jfriend00/euBWV
为了解析出“Storage”类名而需要的"[object Storage]"
值来自ECMAScript spec 中定义的内部[[Class]]
属性。在第 8.6.2 节中,规范为 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String"
定义了特定的类名称。它没有为 localStorage
等主机对象定义类名,因此要么留给个别浏览器,要么在其他规范文档中找到。
此外,规范中提到了[[Class]]
:
[[Class]] 内部属性的值在内部用于 区分不同种类的物体。请注意,本规范 不为程序提供任何访问该值的方法,除非 通过 Object.prototype.toString(见 15.2.4.2)。
而且,我们在 15.2.4.2 中找到了使用 [[Class]
作为第二个字来生成像 [object Array]
或 [object String]
这样的输出的规范。
所以,Object.prototype.toString
应该是这样工作的。显然 IE8 在 localStorage
对象方面存在这方面的错误。我们无法知道 IE8 内部是否toString()
没有使用[[Class]]
或者[[Class]]
是否设置不正确。无论如何,IE8 中的console.log()
似乎没有直接使用Object.prototype.toString()
,因为它会产生不同的结果。
"" + obj
变通办法的行为更难理解。该规范描述了对象到字符串的类型强制应该如何工作。在规范中一直遵循线程有点复杂,因为一个部分依赖于另一个部分,而另一个部分依赖于另一个部分,依此类推。但是,最后,它执行内部方法ToString(ToPrimitive(input argument, hint String))
,显然在 IE8 中,ToPrimitive
当传递一个我们想要一个字符串的提示时给了我们Object.prototype.toString()
不是的实际类名。规范中有一条路径蜿蜒穿过[[DefaultValue]]
,这可能是 IE8 中发生这种情况的方式,但由于我们已经知道 IE8 没有遵循规范的第一部分,而且它通常也不擅长遵循规范,假设它在这方面遵循规范不是一个有效的假设。最后,我们只知道在 IE8 中对字符串的类型强制最终会得到我们想要的 [[Class]]
。
作为一个有趣的测试,我在 Chrome 浏览器中尝试了我的测试套件,通过"" + obj
变通方法运行所有作为对象的测试用例(通常代码仅在Object.prototype.toString()
不返回时使用该路径"Object"
以外的名称。它适用于除数组之外的所有内容。我认为这意味着对象的 [[DefaultValue]]
通常为 [[Class]]
(除非对象类型决定它具有更好的默认值,Array
显然是这样) . 所以,我认为我们已经确认修复 IE8 的变通办法实际上应该按照规范工作。所以,它不仅是 IE8 的变通办法,而且是到达[[Class]]
的替代途径如果对象类型没有实现不同的默认值,则为其命名。
所以,我通过规范提出的这个新代码实际上是这个伪代码:
-
尝试使用
Object.prototype.toString()
获取内部变量[[Class]]
如果这给了我们"Object"
以外的其他东西,那么使用它
否则,使用"" + obj
尝试获取[[DefaultValue]]
的字符串版本
如果返回有用的东西,请使用它
如果我们仍然没有比"Object"
更有用的东西,那么只需返回"Object"
【讨论】:
添加了来自 ECMAScript 规范的关于我们所追求的[object Storage]
格式的信息。
哇,感谢您为深入研究 ES 规范所做的努力。我现在不能测试它,但如果隐式转换/调用真的在 IE8 中有效,它会非常整洁。但是,我不喜欢夸大.type
方法,尤其是使用regex
以及出于性能原因的更多功能。无论如何,大 +1 和赏金是你的。谢谢
欢迎您替换正则表达式。你的方法不是很安全,因为如果它得到了没有空格的意外输入,它会抛出一个异常,这就是为什么我做了一些不同的事情。如果您想对输入进行一点检查,则可以在没有正则表达式的情况下使您的输入更安全。除了正则表达式之外,这里几乎没有任何臃肿。唯一额外的代码路径是当类型返回“Object”时,它会尝试强制转换以查看是否有另一种方法可以获得有意义的类型。而且,window
比较现在是安全的,适用于所有窗口对象,而不仅仅是全局对象。
正则表达式调用本身似乎是一个亮点。我没有对其进行基准测试,但我很确定正则表达式要慢得多。但我会用剩下的。
@jAndy - 好的,我删除了正则表达式并使用了一些可能比你更快的东西。【参考方案2】:
你写的:
只有当您尝试将
localStorage
与window
进行比较时才会发生这种情况,您可以将其与其他任何内容进行比较而不会遇到任何问题。
那你为什么不这样做呢?
var ToStr = Object.prototype.toString;
Object.type = function _type( obj )
if ( window.localStorage && obj === window.localStorage )
return 'Storage';
if ( obj === window )
return 'Window';
var res = ToStr.call( obj ).split( ' ' )[ 1 ].replace( ']', '' );
if ( res === 'Window' || res === 'Global' )
return 'Undefined';
if ( res.indexOf( 'HTML' ) === 0 )
return 'Node';
return res;
;
另外直接回答问题:
-
“...或者我可以以某种方式解决这个问题吗?”:你不能。如果浏览器存在比较两个特殊值的错误,则无法在 javascript 代码中修复此问题....
“您如何知道在 IE8(也可能是 IE9)中处理的是 localStorage 对象?”如果您正在处理它,您只需比较
obj === window.localStorage
即可检查。没有比这更简单的了,不是吗?
“IE8 控制台如何打印出正确的 [[Class]]?”与 javascript 相比,内部函数对这些对象的访问方式非常不同......你不能在那里做同样的事情。
问候, 史蒂芬
【讨论】:
我当然可以这样做,但我认为这不是解决方案。我不想进行特殊比较,因为一个浏览器吓坏了,所有其他浏览器都将Storage
视为一种类型
@jAndy - 这是您处理旧浏览器中的错误的方法。为使 IE8 正常工作的 localStorage 添加一个特殊情况绝对没有错。如果你能找到一个不需要这个的解决方法,那么你的力量就会更大,但如果没有这个,这个答案对我来说是有意义的。 IE8 又老又臭。就个人而言,我会花尽可能少的时间来寻找解决方案。
@jfriend00:我很欣赏这些建议,你可能是对的,但是由于这个回复没有回答我的任何问题,这将获得赏金奖励,我会很不高兴看到这个答案得到了赏金。
我没有直接回复问题中的答案是正确的,我现在添加了。但是,我什至对赏金都不感兴趣,所以请等待更适合您的答案。但是,如果没有这样的答案并且赏金时间结束并且您使用我的方法,请接受它,或者添加您自己的解决方案并接受它。请不要只留下信息,因为我认为没有什么可以讨论或解决的。 (如果不添加特殊的解决方法代码,根本无法解决特殊的错误。)
@SteffenHeil:“根本没有办法解决一个特殊的错误”,你确定还是只是猜测?如果有人可以解释这种行为,可能会有更好/真正的解决方法【参考方案3】:
应使用特征检测(见下文)以避免尝试访问 localStorage
due to browser policies etc. 失败
至于特定于 IE8 的问题,您能否确认该页面正在提供,而不是在本地打开?即 URL 是 http://localhost/hello.html
而不是 file:///C:/somefolder/hello.html
IE8 不允许 localStorage
用于本地打开的文件,尽管找不到官方文档来支持这一点(但有 this 和 this :) 另外,可能值得检查一下您没有在 IE7 模式下运行浏览器。
如果您上面的代码应该检测功能可用性而不是完全检测其他东西,则可以选择使用the following:
// Feature test
var hasStorage = (function()
try
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
catch(e)
return false;
());
【讨论】:
【参考方案4】:这确实是一个很奇怪的IE8
bug! (哦,我喜欢 IE 什么)!
正如 IE8
浏览器的 cmets 中所述,我认为只有一种解决方案:
typeof obj === "object" && obj.constructor.toString() === "[object Storage]"
检查前请确保当前浏览器为IE8,否则无法正常工作,即使在其他版本的IE下也不行!
附: IE 与其“兄弟”兼容程度的绝佳示例!
【讨论】:
【参考方案5】:这一点都不漂亮。我可以获得字符串"[object Storage]"
的唯一方法是使用以下代码:
obj.constructor.toString();
对于任何本机构造函数,输出(以及其他浏览器中的预期输出)是本机函数的字符串表示形式。但是,我们在这里讨论的是主机对象,其中完全适用不同的规则。有趣的是,尽管对 DOM 进行了所有改进,IE 9 给出了相同的结果。
这不是一个完全有弹性的解决方案,但它是我能在短时间内找到的唯一解决方案。
似乎 IE 8 和 IE 9 的 IE 8 文档模式之间存在差异,localStorage.constructor
在前者中实际上并不存在。在那种情况下,我认为没有其他可行的解决方案。 Duck 类型似乎并不有效,因为所有 localStorage
对象的属性名称都有些通用。你可以使用
window.localStorage === obj
但我不确定 IE 8 在尝试覆盖窗口对象的本机属性时的行为(如果它不允许,那么你可能没问题)。
【讨论】:
在我的 IE9 中,Object.prototype.toString.call( window.localStorage )
确实返回 [object Storage]
...(在 IE8 和 IE7 模式下,我得到 [object Object]
)
window.localStorage.constructor === undefined
在我的 IE8 中?
@ŠimeVidas:是的,我只是提请注意localStorage.constructor.toString()
不会像其他浏览器那样返回本机函数的字符串表示形式,即使在 IE 9 中也是如此。正如您所说,返回对象内部 [[Class]]
属性的常规方法在 IE 9 中按预期工作,因此应该不是问题。
我在刚才的问题上写了一条评论,暗示localStroage属性可能是由ActiveX支持的(即不是原生JavaScript),看了上面的cmets之后,看起来我很可能是正确的。
以上是关于奇怪的 IE8 内部 [[ class ]] 属性行为的主要内容,如果未能解决你的问题,请参考以下文章