Подтвердить что ты не робот

Weird IE8 внутреннее поведение [[class]]

Недавно я столкнулся с некоторыми проблемами с IE8 (я не знаю о 9 на данный момент) с чтением и сравнением значений некоторых свойств [[Class]]. Фактически это только в случае для объекта 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, потому что он называется довольно часто.

Чтобы полностью запутать меня здесь: Когда вы делаете console.log( obj ) в консоли IE8, он возвращает вас [object Storage] (nice!), но если вы вызываете Object.prototype.toString.call( obj ), он возвращает [object Object]. То же самое касается typeof obj, вернет Object.

Второй вопрос:

Как IE8 console распечатывает правильный [[Class]]?

4b9b3361

Ответ 1

Я нашел способ обойти поведение IE8 с помощью неявной операции toString(), а спецификация 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 (фреймы, iframes, popups и т.д.), Поэтому таким образом он вернет "Окно", для любого объекта окна.

Здесь код:

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

Желаемое значение "[object Storage]", которое вы использовали для разбора имени класса "Хранение", поступает из внутреннего свойства [[Class]], как определено в спецификации ECMAScript. В разделе 8.6.2 спецификация определяет определенные имена классов для "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", and "String". Он не определяет имена классов для объектов хоста, таких как localStorage, поэтому он либо остается отдельным браузером, либо находится в каком-либо другом документе спецификации.

Далее, spec говорит об этом [[Class]]:

Значение внутреннего свойства [[Class]] используется внутри различать различные типы объектов. Обратите внимание, что эта спецификация не предоставляет каких-либо средств для программы для доступа к этой через Object.prototype.toString(см. 15.2.4.2).

И именно в 15.2.4.2 мы находим спецификацию для генерации вывода, например [object Array] или [object String], используя [[Class] в качестве второго слова.

Итак, Object.prototype.toString - это то, как он должен работать. Очевидно, что IE8 имеет ошибки в этом отношении для объекта localStorage. Мы не можем знать внутри IE8, не работает ли toString() [[Class]] или не установлен ли [[Class]]. В любом случае кажется, что console.log() в IE8 напрямую не использует 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]] name, если тип объекта не реализует другое значение по умолчанию.

Итак, действительно, что этот новый код, который я предложил, делает через spec, это псевдо-код:

  • Попробуйте получить внутреннюю переменную [[Class]] с помощью Object.prototype.toString()
  • Если это дает нам нечто иное, чем "object", тогда используйте его
  • В противном случае используйте "" + obj, чтобы попытаться получить строчную версию [[DefaultValue]]
  • Если это возвращает что-то полезное, используйте его
  • Если у нас еще нет чего-то более полезного, чем "object", просто верните "object"

Ответ 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 распечатывает правильный [[Класс]]?" Внутренняя функция имеет совсем другой вид доступа к этим объектам, чем javascript... Вы не можете делать то же самое там.

С уважением, Штеффен

Ответ 3

Следует использовать обнаружение функции (см. ниже), чтобы избежать неудачных попыток доступа к localStorage из-за политик браузера и т.д.

Что касается проблемы с IE8-спецификой, можете ли вы подтвердить, что страница обслуживается, а не открыта локально? т.е. URL-адрес http://localhost/hello.html, а не file:///C:/somefolder/hello.html IE8 не разрешает localStorage для файлов, открытых локально, хотя официальная документация не может быть найдена для подтверждения этого (но там this и this:) Также, возможно, стоит проверить, что вы не используете браузер в режиме IE7.

Если ваш код выше должен обнаруживать доступность функций, а не что-то другое, было бы возможно использовать что-то вроде следующее

// Feature test
var hasStorage = (function() {
  try {
    localStorage.setItem(mod, mod);
    localStorage.removeItem(mod);
    return true;
  } catch(e) {
    return false;
  }
}());

Ответ 4

Это совсем не так. Единственный способ получить строку "[object Storage]" - это использовать следующий код:

obj.constructor.toString();

Для любого нативного конструктора вывод и ожидаемый вывод в других браузерах - это строковое представление нативной функции. Однако мы говорим о объектах хоста здесь, где применяются разные правила. Интересно, что несмотря на все его улучшения в DOM, IE 9 дает тот же результат.

Это не полностью устойчивое решение, но это единственное решение, которое я смог найти за короткое время, которое я должен был рассмотреть.


Кажется, что несоответствие между IE 8 и IE 9 IE8, и localStorage.constructor фактически не существует в первом. В этом случае я не думаю, что есть еще одно жизнеспособное решение. Утиная печать не выглядит так, как если бы все имена свойств объекта localStorage были несколько родовыми. Вы можете просто использовать

window.localStorage === obj

Но я не уверен в поведении IE 8 при попытке переопределить собственные свойства объекта window (если он этого не позволяет, тогда вы можете быть в порядке).

Ответ 5

Это действительно очень странная ошибка IE8! (о, что я люблю IE)!

Как указано в комментариях для IE8 браузеров, на мой взгляд есть только одно решение:

typeof obj === "object" && obj.constructor.toString() === "[object Storage]"

Убедитесь, что текущий браузер IE8 перед проверкой, не работает иначе, даже в других версиях IE!

P.S. Отличный пример того, как совместимый IE со своими "братьями"!