2011-05-03 15 views
15

actualización: Estoy reformular esta pregunta, ya que el punto importante para mí es identificar el objeto literal:¿Cómo puedo diferenciar entre un objeto literal y otros objetos Javascript?

¿Cómo puedo saber la diferencia entre un objeto literal y cualquier otro objeto Javascript (por ejemplo, un nodo DOM, un objeto Date, etc.)? ¿Cómo puedo escribir esta función:

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

Así que sólo imprime Object literal! como resultado de la primera llamada a continuación:

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

pregunta original

estoy escribiendo una función Javascript que está diseñada para aceptar un objeto literal, una cadena o un nodo DOM como su argumento. Necesita manejar cada argumento de forma ligeramente diferente, pero por el momento no puedo encontrar la manera de diferenciar entre un nodo DOM y un antiguo simple objeto literal.

Aquí es una versión muy simplificada de mi función, junto con una prueba para cada tipo de argumento que necesito para manejar:

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); 

Este código registrará el mismo mensaje para los segundos dos llamadas. No puedo entender qué incluir en la cláusula else if. He intentado otras variaciones como x instanceof Object que tienen el mismo efecto.

Entiendo que este podría ser un mal diseño de API/código de mi parte. Incluso si lo es, aún me gustaría saber cómo hacer esto.

+0

http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object – Ben

+0

@Ben, ahora que tengo actualizó la pregunta, no creo que sea un duplicado de esa. Pero con mucho gusto lo cerraré si es un duplicado de una pregunta diferente. –

+0

Esto es casi un duplicado, pero ¿hay alguna solución que no cause falsos positivos cuando se le da un objeto definido por el usuario? http://stackoverflow.com/questions/4320767/check-that-value-is-object-literal –

Respuesta

32

¿Cómo puedo indique la diferencia entre un objeto literal y cualquier otro objeto Javascript (por ejemplo, un nodo DOM, un objeto Date, etc.)?

La respuesta corta es que no puede.

Un objeto literal es algo así como:

var objLiteral = {foo: 'foo', bar: 'bar'}; 

mientras que el mismo objeto creado usando el constructor de objetos podría ser:

var obj = new Object(); 
obj.foo = 'foo'; 
obj.bar = 'bar'; 

no creo que hay alguna fiable forma de ver la diferencia entre cómo se crearon los dos objetos.

¿Por qué es importante?

Una estrategia de prueba de características generales es probar las propiedades de los objetos pasados ​​a una función para determinar si son compatibles con los métodos que deben invocarse. De esta forma, realmente no te importa cómo se crea un objeto.

Puede emplear el "tipado de pato", pero solo de forma limitada. No puede garantizarlo solo porque un objeto tenga, por ejemplo, un método getFullYear() que sea un objeto Date. Del mismo modo, solo porque tiene una propiedad nodeType no significa que sea un objeto DOM.

Por ejemplo, la función jQuery isPlainObject piensa que si un objeto tiene una propiedad nodeType, es un nodo DOM, y si tiene una propiedad setInterval es un objeto Window. Ese tipo de mecanografía de pato es extremadamente simplista y fallará en algunos casos.

También puede observar que jQuery depende de que las propiedades se devuelvan en un orden específico - otra suposición peligrosa que no es compatible con ningún estándar (aunque algunos partidarios están tratando de cambiar el estándar para adaptarse a su supuesto comportamiento).

Editar 22-Apr-2014: En la versión 1.10 jQuery incluye un support.ownLast propiedad basado en las pruebas de una sola propiedad (al parecer esto es para apoyo IE9) para ver si las propiedades heredadas se enumeran primera o la última. Esto continúa ignorando el hecho de que las propiedades de un objeto pueden devolverse en cualquier orden, independientemente de si son heredadas o propias, y pueden estar mezcladas.

Probablemente, la prueba más simple para los objetos "natural" es:

function isPlainObj(o) { 
    return typeof o == 'object' && o.constructor == Object; 
} 

que siempre será cierto para los objetos creados con objetos literales o el constructor de objetos, pero también puede dar resultados falsos para objetos creados otras formas y puede (probablemente) fallar en todos los cuadros. También podría agregar una prueba instanceof, pero no puedo ver que haga algo que la prueba de constructor no haga.

Si está pasando objetos ActiveX, lo mejor es envolverlo en try..catch ya que pueden devolver todo tipo de resultados extraños, incluso arrojar errores.

Edición 13-oct-2015

Por supuesto, hay algunas trampas:

isPlainObject({constructor: 'foo'}); // false, should be true 

// In global scope 
var constructor = Object; 
isPlainObject(this);  // true, should be false 

ensuciar con la propiedad constructor causará problemas. También existen otras trampas, como objetos creados por constructores distintos de Object.

Como ES5 ahora es bastante omnipresente, hay Object.getPrototypeOf para verificar el [[Prototype]] de un objeto. Si es el objeto Object.prototype, entonces el objeto es un objeto simple. Sin embargo, algunos desarrolladores desean crear objetos verdaderamente "vacíos" que no tengan propiedades heredadas. Esto se puede hacer usando:

var emptyObj = Object.create(null); 

En este caso, la propiedad es [[Prototype]] nula. Así que simplemente verificando si el prototipo interno es Object.prototype no es suficiente.

También existe la ampliamente utilizada razonablemente:

Object.prototype.toString.call(valueToTest) 

que se especificó como devolver una cadena basada en la [[Class]] propiedad interna, que es para los objetos [object Object]. Sin embargo, eso ha cambiado en ECMAScript 2015, por lo que las pruebas se realizan para otros tipos de objetos y el valor predeterminado es [object Object], por lo que el objeto puede no ser un "objeto simple", solo uno que no se reconoce como algo más. Por lo tanto, la especificación señala que:

"[toString pruebas utilizando] no proporciona un mecanismo de tipo de pruebas fiables para otros tipos de objetos incorporados o programa definidos"

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.prototype.tostring

lo tanto una función de actualización que permite anfitriones pre-ES5, objetos con un [[Prototype]] de tipos de objetos nulos y otros que no tienen getPrototypeOf (como nula, gracias Chris Nielsen) Esta abajo.

Nota que no hay manera de polyfill getPrototypeOf, por lo que puede no ser útil si se requiere apoyo para navegadores anteriores (por ejemplo IE 8 e inferior, de acuerdo con 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>'); 
 
});

+0

Gracias por la respuesta completa (que en su mayoría confirmó mis suposiciones). Creo que su función 'isPlainObj' es lo suficientemente precisa para mis propósitos. ¡Muy agradecido! –

+0

Tenga en cuenta que 'typeof null ===" object "' y 'Object.getPrototypeOf (null)' arrojará un error. Si desea que la entrada 'null' devuelva falso en lugar de arrojar un error, su función' isPlainObject' debe incluir una verificación para 'null'. –

+0

@ ChrisNielsen, gracias por traerme de regreso a esto. Dado que desde ES5 ha sido posible crear objetos con * null * como su '[[Prototype]]' usando 'Object.create (null)', lo anterior ahora está muy desactualizado y se actualizará tan pronto como yo tener tiempo. – RobG

1

Mueva la comprobación del nodo DOM por encima del literal del objeto. Compruebe alguna propiedad que exista en un nodo DOM para detectar un nodo. Estoy usando el nodeType. No es muy infalible ya que podría pasar en un objeto {nodeType: 0 } y eso rompería esto.

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

Todos los cheques de tipificación de pato como la de arriba e incluso instanceof cheques están destinados al fracaso. Para determinar realmente si el objeto dado es en realidad un nodo DOM, debe usar algo que no sea el objeto pasado por sí mismo.

+0

Lo siento, pero actualicé mi pregunta después de que la respondió porque me di cuenta de que necesitaba enfatizar el hecho de que la identificación precisa de los literales de objeto, si es posible, es mi objetivo real. –

2

Dado que todos los nodos DOM heredan de la interfaz Node puede probar lo siguiente:

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

Pero no estoy seguro de si esto funciona en las versiones anteriores de Internet Explorer

+2

Usted no sabe eso. Si bien puede tener sentido implementar objetos DOM usando herencia prototipo que imite el esquema sugerido por las especificaciones DOM del W3C, no hay razón para esperar que ** todos los navegadores ** lo hagan. De hecho, los navegadores no tienen que implementar ningún tipo de herencia para los objetos host. – RobG

+1

Bueno, lo he probado en Chrome (por lo que probablemente también funcionará en Safari), en Firefox y en IE9, por lo que parece que al menos todos los navegadores modernos funcionan de esta manera. – standardModel

+0

@standardModel @RobG Me acabo de encontrar [un artículo en profundidad de kangax] (http://perfectionkills.com/whats-wrong-with-extinging-the-dom/#lack_of_specification) que advierte contra 'instanceof Node '. Aunque no conozco ninguna alternativa confiable. – tomekwi

1

Tal vez algo como esto?

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

    return false; 
}; 

O este otro enfoque:

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

Similar al ejemplo @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 
} 

PRUEBA:

function isPlainObject(obj) { 
 
\t return \t typeof obj === 'object' 
 
\t \t && obj !== null 
 
\t \t && obj.constructor === Object 
 
\t \t && 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>'); 
 
});

Cuestiones relacionadas