views:

830

answers:

5

Is there any way to determine in Javascript if an object was created using object-literal notation or using a constructor method?

It seems to me that you just access it's parent object, but if the object you are passing in doesn't have a reference to it's parent, I don't think you can tell this, can you?

+2  A: 

There is no way to tell the difference between an object built from an object literal, and one built from other means.

It's a bit like asking if you can determine whether a numeric variable was constructed by assigning the value '2' or '3-1';

If you need to do this, you'd have to put some specific signature into your object literal to detect later.

Paul Dixon
A: 

An object literal is the notation you use to define an object - which in javascript is always in the form of a name-value pair surrounded by the curly brackets. Once this has been executed there is no way to tell if the object was created by this notation or not (actually, I think that might be an over-simplification, but basically correct). You just have an object. This is one of the great things about js in that there are a lot of short cuts to do things that might be a lot longer to write. In short, the literal notation replaces having to write:

var myobject = new Object();
Steve Mc
+1  A: 

It sounds like you are looking for this:

function Foo() {}

var a = {};
var b = new Foo();

console.log(a.constructor == Object); // true
console.log(b.constructor == Object); // false

The constructor property on an object is a pointer to the function that is used to construct it. In the example above b.constructor == Foo. If the object was created using curly brackets (the array literal notation) or using new Object() then its constructor property will == Object.

Update: crescentfresh pointed out that $(document).constructor == Object rather than being equal to the jQuery constructor, so I did a little more digging. It seems that by using an object literal as the prototype of an object you render the constructor property almost worthless:

function Foo() {}
var obj = new Foo();
obj.constructor == Object; // false

but:

function Foo() {}
Foo.prototype = { objectLiteral: true };
var obj = new Foo();
obj.constructor == Object; // true

There is a very good explanation of this in another answer here, and a more involved explanation here.

I think the other answers are correct and there is not really a way to detect this.

Prestaul
`constructor === Object` is true for lots of objects. For example try `javascript:alert($(document).constructor === Object)` on this page, even though `jQuery !== Object`.
Crescent Fresh
I've updated my answer based on crescentfresh's comment.
Prestaul
+7  A: 

Edit: I'm interpreting "object literal" as anything created using an object literal or the Object constructor. This is what John Resig most likely meant.

I have a function that will work even if .constructor has been tainted or if the object was created in another frame. Note that Object.prototype.toString.call(obj) === "[object Object]" (as some may believe) will not solve this problem.

function isObjectLiteral(obj) {
    if (typeof obj !== "object" || obj === null)
     return false;

    var hasOwnProp = Object.prototype.hasOwnProperty,
    ObjProto = obj;

    // get obj's Object constructor's prototype
    while (Object.getPrototypeOf(ObjProto = Object.getPrototypeOf(ObjProto)) !== null);

    if (!Object.getPrototypeOf.isNative) // workaround if non-native Object.getPrototypeOf
     for (var prop in obj)
      if (!hasOwnProp.call(obj, prop) && !hasOwnProp.call(ObjProto, prop)) // inherited elsewhere
       return false;

    return Object.getPrototypeOf(obj) === ObjProto;
};


if (!Object.getPrototypeOf) {
    if (typeof ({}).__proto__ === "object") {
     Object.getPrototypeOf = function (obj) {
      return obj.__proto__;
     };
     Object.getPrototypeOf.isNative = true;
    } else {
     Object.getPrototypeOf = function (obj) {
      var constructor = obj.constructor,
      oldConstructor;
      if (Object.prototype.hasOwnProperty.call(obj, "constructor")) {
       oldConstructor = constructor;
       if (!(delete obj.constructor)) // reset constructor
        return null; // can't delete obj.constructor, return null
       constructor = obj.constructor; // get real constructor
       obj.constructor = oldConstructor; // restore constructor
      }
      return constructor ? constructor.prototype : null; // needed for IE
     };
     Object.getPrototypeOf.isNative = false;
    }
} else Object.getPrototypeOf.isNative = true;

Here is the HTML for the testcase:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <!-- Online here: http://code.eligrey.com/testcases/all/isObjectLiteral.html -->
    <title>isObjectLiteral</title>
    <style type="text/css">
    li { background: green; } li.FAIL { background: red; }
    iframe { display: none; }
    </style>
</head>
<body>
<ul id="results"></ul>
<script type="text/javascript">
function isObjectLiteral(obj) {
    if (typeof obj !== "object" || obj === null)
     return false;

    var hasOwnProp = Object.prototype.hasOwnProperty,
    ObjProto = obj;

    // get obj's Object constructor's prototype
    while (Object.getPrototypeOf(ObjProto = Object.getPrototypeOf(ObjProto)) !== null);

    if (!Object.getPrototypeOf.isNative) // workaround if non-native Object.getPrototypeOf
     for (var prop in obj)
      if (!hasOwnProp.call(obj, prop) && !hasOwnProp.call(ObjProto, prop)) // inherited elsewhere
       return false;

    return Object.getPrototypeOf(obj) === ObjProto;
};


if (!Object.getPrototypeOf) {
    if (typeof ({}).__proto__ === "object") {
     Object.getPrototypeOf = function (obj) {
      return obj.__proto__;
     };
     Object.getPrototypeOf.isNative = true;
    } else {
     Object.getPrototypeOf = function (obj) {
      var constructor = obj.constructor,
      oldConstructor;
      if (Object.prototype.hasOwnProperty.call(obj, "constructor")) {
       oldConstructor = constructor;
       if (!(delete obj.constructor)) // reset constructor
        return null; // can't delete obj.constructor, return null
       constructor = obj.constructor; // get real constructor
       obj.constructor = oldConstructor; // restore constructor
      }
      return constructor ? constructor.prototype : null; // needed for IE
     };
     Object.getPrototypeOf.isNative = false;
    }
} else Object.getPrototypeOf.isNative = true;

// Function serialization is not permitted
// Does not work across all browsers
Function.prototype.toString = function(){};

// The use case that we want to match
log("{}", {}, true);

// Instantiated objects shouldn't be matched
log("new Date", new Date, false);

var fn = function(){};

// Makes the function a little more realistic
// (and harder to detect, incidentally)
fn.prototype = {someMethod: function(){}};

// Functions shouldn't be matched
log("fn", fn, false);

// Again, instantiated objects shouldn't be matched
log("new fn", new fn, false);

var fn2 = function(){};

log("new fn2", new fn2, false);

var fn3 = function(){};

fn3.prototype = {}; // impossible to detect (?) without native Object.getPrototypeOf

log("new fn3 (only passes with native Object.getPrototypeOf)", new fn3, false);

log("null", null, false);

log("undefined", undefined, false);


/* Note:
 * The restriction against instantiated functions is
 * due to the fact that this method will be used for
 * deep-cloning an object. Instantiated objects will
 * just have their reference copied over, whereas
 * plain objects will need to be completely cloned.
 */

var iframe = document.createElement("iframe");
document.body.appendChild(iframe);

var doc = iframe.contentDocument || iframe.contentWindow.document;
doc.open();
doc.write("<body onload='window.top.iframeDone(Object);'>");
doc.close();

function iframeDone(otherObject){
    // Objects from other windows should be matched
    log("new otherObject", new otherObject, true);
}

function log(msg, a, b) {
  var pass = isObjectLiteral(a) === b ? "PASS" : "FAIL";

  document.getElementById("results").innerHTML +=
    "<li class='" + pass + "'>" + msg + "</li>";
}


</script>
</body>
</html>
Eli Grey
Great answer, and it is nice to have you on stackoverflow. Off topic, but thanks also for the e4x array methods.
Prestaul
Now that I've read your answer, I **think** I understand the question.
tvanfosson
Whoa! what's the 3x equal sign about (===)?
leeand00
@leeand00 It means "exactly equal to". For example: var x = {valueOf:function(){return 1}}; (x == 1) === true; (x === 1) === true;
Eli Grey
The second example was supposed to end with === false I meant.
Eli Grey
A: 

I just came across this question and thread during a sweet hackfest that involved a grail quest for evaluating whether an object was created with {} or new Object() (i still havent figured that out.)

Anyway, I was suprised to find the similarity between the isObjectLiteral() function posted here and my own isObjLiteral() function that I wrote for the Pollen.JS project. I believe this solution was posted prior to my Pollen.JS commit, so - hats off to you! The upside to mine is the length... less then half (when included your set up routine), but both produce the same results.

Take a look:

function isObjLiteral(_obj) {
  var _test  = _obj;
  return (  typeof _obj !== 'object' || _obj === null ?
              false :  
              (
                (function () {
                  while (!false) {
                    if (  Object.getPrototypeOf( _test = Object.getPrototypeOf(_test)  ) === null) {
                      break;
                    }      
                  }
                  return Object.getPrototypeOf(_obj) === _test;
                })()
              )
          );
}

Additionally, some test stuff:

var _cases= {
    _objLit : {}, 
    _objNew : new Object(),
    _function : new Function(),
    _array : new Array(), 
    _string : new String(),
    _image : new Image(),
    _bool: true
};

console.dir(_cases);

for ( var _test in _cases ) {
  console.group(_test);
  console.dir( {
    type:    typeof _cases[_test], 
    string:  _cases[_test].toString(), 
    result:  isObjLiteral(_cases[_test])  
  });    
  console.groupEnd();
}

Or on jsbin.com...

http://jsbin.com/iwuwa

Be sure to open firebug when you get there - debugging to the document is for IE lovers.

Rick
@Rick, this solution seems more elegant.
leeand00
@leeand00: It's about the same length as mine. I just included an implementation of Object.getPrototypeOf in mine too, which this one __doesn't__ include.
Eli Grey
Also, if you remove the `Object.getPrototypeOf.isNative` part, mine is only 6 SLOC.
Eli Grey