Как определить разницу между литералом объекта и любым другим объектом Javascript (например, узлом DOM, объектом Date и т. Д.)?
Короткий ответ: ты не можешь.
литерал объекта выглядит примерно так:
var objLiteral = {foo: 'foo', bar: 'bar'};
, тогда как тот же объект, созданный с помощью Конструктор объекта , может быть:
var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
Я не думаю, что существует какой-либо надежный способ определить разницу между тем, как были созданы два объекта.
Почему это важно?
Общая стратегия тестирования возможностей заключается в проверке свойств объектов, передаваемых в функцию, чтобы определить, поддерживают ли они методы, которые должны быть вызваны. Таким образом, вам все равно, как создается объект.
Вы можете использовать "типизацию утки", но только в ограниченной степени. Вы не можете гарантировать это только потому, что у объекта есть, например, метод getFullYear()
, что он является объектом Date. Точно так же то, что оно имеет свойство nodeType , не означает, что это объект DOM.
Например, функция jQuery isPlainObject
считает, что если у объекта есть свойство nodeType, то это DOM-узел, а если у него есть свойство setInterval
, это объект Window. Такой тип утки очень прост и в некоторых случаях не удастся.
Вы также можете заметить, что jQuery зависит от свойств, возвращаемых в определенном порядке - еще одно опасное предположение, которое не поддерживается ни одним стандартом (хотя некоторые сторонники пытаются изменить стандарт в соответствии со своим предполагаемым поведением).
Редактирование 22-апр-2014: в версии 1.10 jQuery включает свойство support.ownLast , основанное на тестировании одного свойства (очевидно, это для поддержки IE9), чтобы увидеть, перечисляются ли унаследованные свойства первыми или последними , При этом по-прежнему игнорируется тот факт, что свойства объекта могут быть возвращены в любом порядке, независимо от того, являются ли они наследуемыми или собственными, и могут быть перемешаны.
Вероятно, самый простой тест для "простых" объектов:
function isPlainObj(o) {
return typeof o == 'object' && o.constructor == Object;
}
Что всегда будет верно для объектов, созданных с использованием литералов объектов или конструктора Object, но вполне может дать ложные результаты для объектов, созданных другими способами, и может (вероятно, будет) давать сбой в рамках фреймов. Вы также можете добавить тест instanceof
, но я не вижу, что он делает что-то, чего не делает тест конструктора.
Если вы передаете объекты ActiveX, лучше обернуть его в try..catch, так как они могут возвращать всевозможные странные результаты, даже выдавать ошибки.
Редактировать 13 октября 2015
Конечно, есть несколько ловушек:
isPlainObject( {constructor: 'foo'} ); // false, should be true
// In global scope
var constructor = Object;
isPlainObject( this ); // true, should be false
Использование свойства конструктора приведет к проблемам. Существуют и другие ловушки, например объекты, созданные конструкторами, отличными от Object.
Поскольку ES5 теперь в значительной степени вездесущ, существует Object.getPrototypeOf , чтобы проверить [[Prototype]]
объекта. Если это buit - в Object.prototype , то объект является простым объектом. Однако некоторые разработчики хотят создавать действительно «пустые» объекты, которые не имеют унаследованных свойств. Это можно сделать с помощью:
var emptyObj = Object.create(null);
В этом случае свойство [[Prototype]]
имеет значение null . Поэтому простой проверки, является ли внутренний прототип Object.prototype , недостаточно.
Существует также достаточно широко используемое:
Object.prototype.toString.call(valueToTest)
, который был указан как возвращающий строку на основе внутреннего свойства [[Class]]
, которое для Objects является [object Object]. Однако это изменилось в ECMAScript 2015, так что тесты выполняются для других типов объектов, и по умолчанию используется [объектный объект], поэтому объект может быть не «простым объектом», а только тем, который не распознается как нечто другое. Спецификация поэтому отмечает, что:
"[тестирование с использованием toString] не обеспечивает надежного типового тестирования
механизм для других типов встроенных или программно определенных объектов. "
http://www.ecma -international.org / ecma-262 / 6.0 / index.html # sec-object.prototype.tostring
Итак, обновленная функция, позволяющая использовать хосты до ES5ниже перечислены объекты с [[Prototype]]
нулями и другие типы объектов, которые не имеют getPrototypeOf (например, null , спасибо Крис Нильсен ).
Обратите внимание, что нет способа заполнить getPrototypeOf , поэтому может оказаться бесполезным, если требуется поддержка старых браузеров (например, IE 8 и ниже, согласно MDN ).
/* Function to test if an object is a plain object, i.e. is constructed
** by the built-in Object constructor and inherits directly from Object.prototype
** or null. Some built-in objects pass the test, e.g. Math which is a plain object
** and some host or exotic objects may pass also.
**
** @param {} obj - value to test
** @returns {Boolean} true if passes tests, false otherwise
*/
function isPlainObject(obj) {
// Basic check for Type object that's not null
if (typeof obj == 'object' && obj !== null) {
// If Object.getPrototypeOf supported, use it
if (typeof Object.getPrototypeOf == 'function') {
var proto = Object.getPrototypeOf(obj);
return proto === Object.prototype || proto === null;
}
// Otherwise, use internal class
// This should be reliable as if getPrototypeOf not supported, is pre-ES5
return Object.prototype.toString.call(obj) == '[object Object]';
}
// Not an object
return false;
}
// Tests
var data = {
'Host object': document.createElement('div'),
'null' : null,
'new Object' : {},
'Object.create(null)' : Object.create(null),
'Instance of other object' : (function() {function Foo(){};return new Foo()}()),
'Number primitive ' : 5,
'String primitive ' : 'P',
'Number Object' : new Number(6),
'Built-in Math' : Math
};
Object.keys(data).forEach(function(item) {
document.write(item + ': ' + isPlainObject(data[item]) + '<br>');
});