奇怪的 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"

只有当您尝试将localStoragewindow 进行比较时才会发生这种情况,您可以将它与其他任何东西进行比较而不会遇到任何问题。这只是另一个错误还是我可以以某种方式解决这个问题?

我想基本上我的问题是:

您如何知道在 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】:

你写的:

只有当您尝试将 localStoragewindow 进行比较时才会发生这种情况,您可以将其与其他任何内容进行比较而不会遇到任何问题。

那你为什么不这样做呢?

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 ]] 属性行为的主要内容,如果未能解决你的问题,请参考以下文章

奇怪的 IE8 布局故障 - 为啥正文背景消失了?

IE8:Div hover 只在设置背景色时有效,很奇怪,为啥?

js兼容ie8-未知的运行时错误

a标签的背景图在ie8下不显示的问题

Class 反射获得构造,方法,属性,内部类等

清除浮动的方法总结