Странное поведение внутреннего атрибута [[class]] в IE8 - PullRequest
13 голосов
/ 26 марта 2012

У меня недавно были некоторые проблемы с 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] (приятно!), Но если вы вызываете Object.prototype.toString.call( obj ), он возвращает [object Object]. То же самое касается typeof obj, вернет object.

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

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

Ответы [ 5 ]

6 голосов
/ 13 апреля 2012

Я нашел способ обойти поведение IE8, используя неявную операцию toString(), и спецификация ECMAScript объясняет, почему обходной путь имеет смысл. Неявное toString() таково:

"" + window.localStorage

Это неявное принудительное обращение к внутреннему методу toString() объекта, и в IE это вернет желаемую форму, которую вы хотите [object Storage], и вы сможете заставить свой код работать без специального регистра window.localStorage.

Итак, я искал способ минимального риска включить это в существующий код. Выбранный подход состоял в том, чтобы получить тип тем же способом, который вы используете для его получения, и тогда и только тогда, когда он возвращает универсальный тип «Объект», тогда посмотрите, есть ли лучшее имя, доступное с новым методом. Итак, все вещи, которые раньше работали просто отлично, будут продолжать работать так же, как и они, и мы могли бы найти лучшее имя для некоторых объектов (например, window.localStorage), которые раньше возвращали общее имя «Объект». Еще одно изменение заключается в том, что я чувствовал себя менее уверенно относительно точного типа возврата, который мы могли бы получить из конструкции "" + obj, поэтому я хотел использовать метод синтаксического анализа, который бы не выдавал ошибку при непредвиденных данных, поэтому я переключился на регулярное выражение из разбиения. / заменить метод, который вы использовали. Регулярное выражение также подтверждает, что это действительно формат [object Type], который кажется желательным.

Затем, чтобы защитить от нечетной проблемы сравнения localStorage === window и получения ошибки, вы можете добавить проверку типа (утка), что объект, не похожий на окно, не пройдет, и это отфильтрует localStorage выпускать и любые другие объекты с той же проблемой. В этом конкретном случае я удостоверяюсь, что тип объекта - "object" и что у него есть свойство с именем setInterval. Мы могли бы выбрать любое хорошо известное, хорошо поддерживаемое свойство объекта window, которое вряд ли будет на любом другом объекте. В этом случае я использую setInterval, потому что это тот же тест, который использует jQuery, когда хочет узнать, является ли объект окном. Обратите внимание, я также изменил код, чтобы он вообще не сравнивался с window, потому что может быть более одного объекта window (фреймы, фреймы, всплывающие окна и т. Д.), Поэтому он вернет «Окно» для любого оконного объекта.

Вот код:

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, поэтому он либо оставляется на усмотрение отдельных браузеров, либо находится в каком-либо другом документе спецификации.

Кроме того, спецификация говорит это о [[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]], если тип объекта не реализует другое значение по умолчанию.

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

  1. Попробуйте получить внутреннюю переменную [[Class]], используя Object.prototype.toString()
  2. Если это дает нам что-то отличное от "Object", тогда используйте это
  3. В противном случае используйте "" + obj, чтобы попытаться получить строковую версию [[DefaultValue]]
  4. Если это возвращает что-то полезное, используйте это
  5. Если у нас все еще нет чего-то более полезного, чем "Object", тогда просто верните "Object"
3 голосов
/ 12 апреля 2012

Вы писали:

Это происходит только в том случае, если вы пытаетесь сравнить 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; 
}; 

Дополнение для прямого ответа на вопросы:

  1. «... или я могу как-то обойти эту проблему?»: Вы не можете. Если в браузере есть ошибка, сравнивающая два специальных значения, вы не можете исправить это в коде JavaScript ...
  2. "Как вы узнаете в IE8 (возможно, и в IE9 тоже), имеете ли вы дело с объектом localStorage?" Вы проверяете, справляетесь ли вы с этим, просто сравнивая obj === window.localStorage. Это не может быть проще, не так ли?
  3. "Как консоль IE8 выводит правильный [[Class]]?" Внутренние функции имеют совершенно иной вид доступа к этим объектам, чем javascript ... Вы не можете делать то же самое там.

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

1 голос
/ 13 апреля 2012

Следует использовать функцию обнаружения (см. Ниже), чтобы избежать неудачных попыток доступа к 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;
  }
}());
0 голосов
/ 11 апреля 2012

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

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

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

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

PS Отличный пример того, насколько IE совместим со своими «братьями и сестрами»!

0 голосов
/ 26 марта 2012

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

obj.constructor.toString();

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

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

<ч />

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

window.localStorage === obj

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

...