2

Is there any way to reliably whether a JavaScript object is an exotic object type, and if so what its type is?

By "exotic", I mean (for the purposes of this question) anything which is could not be created using Object.create. This includes everything the ES2016 spec defines as exotic (any "object that does not have the default behaviour for one or more of the essential internal methods") plus anything created by the ObjectCreate specification method with a non-empty internalSlotsList, plus any kind of host object.

By "reliable", I mean not subject to being tricked by adding / removing properties from the object, using Object.create or Object.setPrototypeOf to give the object an unexpected prototype, modifying the object's @@toStringTag, or a constructor's @@hasInstance. This is important for library functions that need to correctly handle arbitrary user data.

(This means, in particular, that instanceof and Object.prototype.isPrototypeOf() are not useful. For example: var a = Object.create(Array.prototype) makes a something that looks and smells like an array—a instanceof Array === true and a.push and a.pop work as expected—but lacks the magic behaviour .length and is easily shown not to be an actual array exotic: Array.isArray(a) === false.)

By "type", I mean roughly what the ECMAScript 5.1 language specification referred to as [[Class]] - i.e., that quality that separates an Array instance, with its special [[DefineOwnProperty]] behaviour, from an ordinary Object.

Examples

Some cases are pretty easy:

  • Array exotic objects can be reliably detected using Array.isArray(a).
  • Functions can be reliably detected using typeof f === 'function'.
    • But is there any way to detect if a function is a bound function, native function or closure?
  • Some other exotic objects can be reliably detected by careful application of methods from their prototype object, e.g.
    • Sets can be detected by calling Set.prototype.has.apply(s, undefined), and seeing whether it throws a TypeError or not.

Is there any general way to make such a detection?

In particular:

  • Is there any general way to determine whether an object is plain or exotic?
  • Is there any general way to determine the type of an exotic object?

I note that Object.toString.apply(o) used to work reasonably well for this purpose: although in many browsers it would lie about the type of host objects, for all the types defined in the ES 5.1 spec it could be counted on to reliably tell you whether the object was a plain [object Object] or an exotic [object <Type>]. In ES6 and later, however, modifying @@toStringTag will subvert this test.

A means of detection which works in any conformant JS implementation would be ideal, but a means that is specific to Node.js would still be useful.

cpcallen
  • 1,834
  • 1
  • 16
  • 27
  • 1
    I think [except for proxies](https://stackoverflow.com/a/36372939/1048572), the trick with the method that tries to use one of these internal slots should work for all exotic objects. – Bergi Sep 18 '17 at 15:26
  • What do you need this for? Do you have a use case in mind or are you just curious? – Bergi Sep 18 '17 at 15:28
  • @Bergi: Unfortunately there are some types, such as Generator instances, that have special internal slots but which do not seem to have method that will non-destructively identify them. – cpcallen Sep 19 '17 at 10:56
  • @Bergi: The use-case is a library to serialise arbitrary JavaScript datastructures (including correctly handling cyclic data, shared substructure and prototype chains). For plain objects this is straight forward, and special-casing Arrays, Dates, RegExps and so on is not hard—but I want to be able to reject objects that contain internal slots that the library does not (or cannot) have special-case code to handle. – cpcallen Sep 19 '17 at 11:04
  • 1
    I think `toString` and `instanceof` are fine for that. Whoever messes with them *wants* his objects to be misidentified and expects them to be mistreated. – Bergi Sep 19 '17 at 12:41

1 Answers1

0

But is there any way to detect if a function is a bound function, native function or closure?

No. They are all plain and simple functions.

Some other exotic objects can be reliably detected by careful application of methods from their prototype object, e.g.

Sets can be detected by calling Set.prototype.has.apply(s, undefined), and seeing whether it throws a TypeError or not.

You can use the instanceof operator to test if an object is an instance of a particular function constructor (or something up its prototype chain).

var mySet = new Set();
console.log(mySet instanceof Set);
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • Unfortunately `instanceof` can only be used to check the prototype chain of the object (and even then only if the user hasn't mucked around using `Symbol.hasInstance`); see new 3rd paragraph of question for why this is not useful in determining type of object itself. – cpcallen Sep 18 '17 at 14:50
  • I don't think the first part of this is entirely correct - the spec for [`Function.prototype.toString`](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function.prototype.tostring) mandates certain behaviour for functions without source text attached, which cannot be emulated by functions which do have source code - specifically, the result must end with `{ [native code] }`, and this cannot be the source code for a normal function. (I think the spec allows the result to have different spacing to that, so strictly speaking a regex might be needed.) – kaya3 Feb 23 '23 at 09:29
  • To be clear, "functions without source code attached" are not only native functions, the `toString` representation will have `{ [native code] }` also for bound functions and proxy functions. – kaya3 Feb 23 '23 at 09:31