Ответ 1
Как я могу рассказать о различии между литералом объекта и любым другим объектом Javascript (например, DOM node, объектом Date и т.д.)?
Короткий ответ: вы не можете.
Литерал объекта имеет значение:
var objLiteral = {foo: 'foo', bar: 'bar'};
тогда как тот же объект, созданный с использованием конструктора Object, может быть:
var obj = new Object();
obj.foo = 'foo';
obj.bar = 'bar';
Я не думаю, что существует какой-либо надежный способ рассказать о различии между тем, как были созданы два объекта.
Почему это важно?
Общая стратегия тестирования функций заключается в проверке свойств объектов, переданных функции, чтобы определить, поддерживают ли они методы, которые должны быть вызваны. Таким образом, вам все равно, как создается объект.
Вы можете использовать "утиную печать", но только в ограниченной степени. Вы не можете гарантировать это только потому, что у объекта есть, например, метод getFullYear()
, который является объектом Date. Аналогично, только потому, что он обладает свойством nodeType, это не означает объект DOM.
Например, функция jQuery isPlainObject
считает, что если у объекта есть свойство nodeType, это DOM node, и если у него есть свойство 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-in Object.prototype, то объект является простым объектом. Однако некоторые разработчики хотят создавать действительно "пустые" объекты, у которых нет наследуемых свойств. Это можно сделать, используя:
var emptyObj = Object.create(null);
В этом случае свойство [[Prototype]]
равно null. Поэтому просто проверить, является ли внутренний прототип Object.prototype недостаточным.
Существует также широко используемое:
Object.prototype.toString.call(valueToTest)
который был указан как возвращающий строку на основе внутреннего свойства [[Class]]
, который для Objects [Object Object]. Тем не менее, это изменилось в ECMAScript 2015, так что тесты выполняются для других типов объектов, а по умолчанию - [object Object], поэтому объект может быть не просто "простым объектом", а только одним, который не распознается как что-то еще. Поэтому в спецификации указывается, что:
"[тестирование с использованием toString] не обеспечивает надежного тестирования типов механизм для других типов встроенных или определенных программными объектами".
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.tostring
Итак, обновленная функция, которая позволяет использовать до-ES5-хосты, объекты с [[Prototype]]
нулевого и других типов объектов, у которых нет getPrototypeOf (например, null, thanks Chris Nielsen) ниже.
Обратите внимание, что нет пути к polyfill 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 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>');
});