Как я могу различить объектный литерал от других объектов Javascript? - PullRequest
23 голосов
/ 04 мая 2011

Обновление : я перефразирую этот вопрос, потому что для меня важным моментом является определение литерала объекта:

Как определить разницу между литералом объекта и любым другим Javascriptобъект (например, узел DOM, объект Date и т. д.)?Как я могу написать эту функцию:

function f(x) {
    if (typeof x === 'object literal')
        console.log('Object literal!');
    else
        console.log('Something else!');
}

, чтобы она печатала только Object literal! в результате первого вызова ниже:

f({name: 'Tom'});
f(function() {});
f(new String('howdy'));
f('hello');
f(document);

Исходный вопрос

Я пишу функцию Javascript, которая предназначена для приема литерала объекта, строки или узла DOM в качестве аргумента.Он должен обрабатывать каждый аргумент немного по-разному, но в настоящий момент я не могу понять, как провести различие между узлом DOM и простым старым литералом объекта.

Вот очень упрощенная версия моей функции, а такжес тестом для каждого типа аргумента, который мне нужно обработать:

function f(x) {
    if (typeof x == 'string')
        console.log('Got a string!');
    else if (typeof x == 'object')
        console.log('Got an object literal!');
    else
        console.log('Got a DOM node!');
}

f('hello');
f({name: 'Tom'});
f(document);

Этот код будет регистрировать одно и то же сообщение для вторых двух вызовов.Я не могу понять, что включить в предложение else if.Я пробовал другие варианты, такие как x instanceof Object, которые имеют тот же эффект.

Я понимаю, что это может быть плохой дизайн API / кода с моей стороны.Даже если это так, я все равно хотел бы знать, как это сделать.

Ответы [ 5 ]

43 голосов
/ 04 мая 2011

Как определить разницу между литералом объекта и любым другим объектом 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>');
});
2 голосов
/ 24 июля 2016

Аналогично примеру @RobG:

function isPlainObject(obj) {
    return  typeof obj === 'object' // separate from primitives
        && obj !== null         // is obvious
        && obj.constructor === Object // separate instances (Array, DOM, ...)
        && Object.prototype.toString.call(obj) === '[object Object]'; // separate build-in like Math
}

TEST:

function isPlainObject(obj) {
	return	typeof obj === 'object'
		&& obj !== null
		&& obj.constructor === Object
		&& Object.prototype.toString.call(obj) === '[object Object]';
}

var data = {
  '{}': {},
  'DOM element': document.createElement('div'),
  'null'       : null,
  'Object.create(null)' : Object.create(null),
  'Instance of other object' : new (function 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 + ':<strong>' + isPlainObject(data[item]) + '</strong><br>');
});
2 голосов
/ 04 мая 2011

Поскольку все DOM-узлы наследуются от интерфейса Node, вы можете попробовать следующее:

if(typeof x === 'string') {
    //string
} else if(x instanceof Node) {
    //DOM Node
} else {
    //everything else
}

Но я не уверен, работает ли это в более старых версиях Internet Explorer

1 голос
/ 24 февраля 2014

Может как то так?

var isPlainObject = function(value){
    if(value && value.toString && value.toString() === '[object Object]')
        return true;

    return false;
};

Или этот другой подход:

var isObject = function(value){
    var json;

    try {
        json = JSON.stringify(value);
    } catch(e){

    }

    if(!json || json.charAt(0) !== '{' || json.charAt(json.length - 1) !== '}')
        return false;

    return true;
};
1 голос
/ 04 мая 2011

Переместите проверку для узла DOM выше литерала объекта.Проверьте некоторое свойство, которое существует на узле DOM, чтобы обнаружить узел.Я использую nodeType.Это не очень надежно, так как вы могли бы передать объект {nodeType: 0 }, и это сломало бы это.

if (typeof x == 'string') { /* string */ }
else if ('nodeType' in x) { /* dom node */ }
else if (typeof x == 'object') { /* regular object */ }

Все проверки на утку, как, например, выше, и даже проверки instanceof неизбежны.Чтобы действительно определить, является ли данный объект на самом деле узлом DOM, вам нужно использовать что-то, кроме самого переданного объекта.

...