# New well-known symbol: Symbol.classType

**URL:** <https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399>\
**Category:** 💡 Ideas\
**Tags:** proposal\
**Created:** [July 19, 2020, 4:34pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399 "2020-07-19T16:34:34Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 19, 2020, 4:34pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/1 "2020-07-19T16:34:34Z")

</div>

There have been other proposals to introduce something like this. Most recently [Symbol.typeofTag](https://es.discourse.group/t/new-well-known-symbol-symbol-typeoftag/105). Since that idea had issues that are hard to get around, what if instead we have a new well-known symbol specifically for the purpose of identifying the constructed type of objects? In lieu of a written explanation, I think the code below will do a better job of explaining what I mean.

```javascript
Object.defineProperty(Symbol, "classType", { value: Symbol() });

class Object {
   constructor() {
      if (new.target) {
         Object.defineProperty(this, Symbol.classType, { value: new.target });
      }
   }
}

```

This should be automatic and embedded into the process of creating any ES Object. Just the act of calling a constructor function should cause the object to be stamped with this Symbol. This would resolve issues using `instanceof` since, unlike the prototype, this value cannot be changed. This would also circumvent limitations in `typeof` since there would always be a check that can be performed to verify the instantiated type of a given object.

This approach would do nothing for objects created via the factory approach as `new.target` would not be available. In the case of a `class` constructor behaving as a factory function, the returned object should be stamped with the `new.target` value if it isn't already present.

This approach allows for the conclusive identification of the "type" of an object without introducing any expensive or intrusive type mechanics in the engine.

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [July 19, 2020, 8:48pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/2 "2020-07-19T20:48:58Z")

</div>

Hi,  
can you please elaborate on the problem that this would solve, the use case where it would be needed? Currently you can already access the constructor that was used to construct an object using just `obj.constructor` if everything is set up normally. You mention that `Symbol.classType`

> [@rdking](#):
>
> would resolve issues using `instanceof` since, unlike the prototype, this value cannot be changed

but where is an immutable constructor reference necessary? If you mess with the prototype, you usually have good reason to do so.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 19, 2020, 9:34pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/3 "2020-07-19T21:34:46Z")

</div>

> [@bergus](#):
>
> can you please elaborate on the problem that this would solve, the use case where it would be needed?

Originally, `instanceof` was usually enough to tell us whether or not something would work. Then came the ability to modify an object's prototype. This led to duck-typing. Now with private fields, even duck-typing isn't sufficient. Between `instanceof` and `typeof`, the problem that needed to be solved has always been "Is the 'type' of this object appropriate for what I'm about to do to it?"

There has always been code that expected an object to be staged a certain way and have specific methods available. However, there has rarely ever been a clean and succinct way of determining that. Only a sealed object could guarantee that the prototype wouldn't change, but that's not often done due to both performance and flexibility reasons. One of the main reasons TypeScript and its ilk exist is due to the limitations of not being able to cleanly identify the type of an object.

With just this one symbol, we can introduce types without introducing a type system. I don't think most of us want to turn ES into a typed language. I'm quite happy that I can ignore types most of the time. However, there are occasions when I need the type of an object to be "well defined".

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 19, 2020, 9:58pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/4 "2020-07-19T21:58:57Z")

</div>

There’s already Symbol.hasInstance - does that not suffice?

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 20, 2020, 2:33am UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/5 "2020-07-20T02:33:26Z")

</div>

If it did, I wouldn't be proposing this. The weakness of `Symbol.hasInstance` is 3-fold:

1. It's just as replaceable as the prototype object of an instance. So it still doesn't give you full assurance that the given object is actually constructed by a given class constructor.
2. It's not at all accessible if the corresponding class constructor is not available in the current scope.
3. It is incapable of granting you the constructor of the class on the occasion when that is desirable.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 20, 2020, 3:00am UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/6 "2020-07-20T03:00:33Z")

</div>

Anything with a symbol is just as mutable and/or forgeable as the constructor property, the [[Prototype]], and Symbol.hasInstance.

Any solution that was more robust than this would be identical to an internal slot or a private field brand check (or any identity-based cache the constructor participated in) - which would have the same issues with Proxy that slots, private fields, and identity do.

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [July 20, 2020, 12:23pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/7 "2020-07-20T12:23:25Z")

</div>

> [@rdking](#):
>
> Originally, `instanceof` was usually enough to tell us whether or not something would work.

It still is "usually enough"! When an object inherits from a certain prototype, we can expect it to implement the methods on that prototype object, either directly or at least in faith (a subtype).

> [@rdking](#):
>
> Then came the ability to modify an object's prototype.

I don't see how that is relevant. Are you saying this is a problem because arbitrary objects could have their prototypes changes to fool your `instanceof` check (despite not being constructed by constructor you're testing for), or is it that you're afraid that an object's prototype is changed _after_ your `instanceof` check and thereby breaking your code?

> [@rdking](#):
>
> The problem that needed to be solved has always been "Is the 'type' of this object appropriate for what I'm about to do to it?" This led to duck-typing.

This is totally appropriate for a language like javascript. You don't care how the object was constructed or what prototype it inherits from, all you need is that it implements the right interface. Yes, it's not succinct to make this check, and the lack of an accessible type system won't let you check for method signatures but only existence, but it's still useful.

> [@rdking](#):
>
> Now with private fields, even duck-typing isn't sufficient.

I don't understand, as private fields are not part of the public interface that duck typing tests for. Notice that in pure OOP, you always interact with object through their methods only, and you do _not_ care how they are implemented internally, so duck typing does usually not test for their structure.

> [@rdking](#):
>
> However, there are occasions when I need the type of an object to be "well defined". Only a sealed object could guarantee that.

You still haven't completely described what you mean by "well defined". Yes, if you don't trust others to manipulate your object without breaking your contracts, you'll have to seal it. But how does introducing `Symbol.classType` help with that?

The weaknesses you describe for `instanceof` would apply to your proposed solution as well:

1. Everyone can fake an object with `{[Symbol.classType]: MyObject, …}` without using the `new MyObject` constructor. (And I'd actually argue that this is desirable)
2. If the constructor is not available, then you can't test `something[Symbol.classType] == MyObject` either
3. `instanceof` doesn't give you the constructor, but `.constructor` does. It's already there.

So if you want to identify your objects as your own, you can still create a custom symbol property on them in your constructor and use your approach for checking the type. But I don't see a reason to do that with _every_ object constructed using `new`.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 20, 2020, 2:56pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/8 "2020-07-20T14:56:33Z")

</div>

> [@bergus](#):
>
> Are you saying this is a problem because arbitrary objects could have their prototypes changes to fool your `instanceof` check (despite not being constructed by constructor you're testing for), or is it that you're afraid that an object's prototype is changed _after_ your `instanceof` check and thereby breaking your code?

I've experienced both.

> [@bergus](#):
>
> You don't care how the object was constructed or what prototype it inherits from, all you need is that it implements the right interface.

Duck-typing isn't enough to ensure that. It works most of the time, but there are still occasions where your API can be fed an object with the desired method names available, yet not the desired functionality. Been there too.

> [@bergus](#):
>
> but it's still useful.

I'm not denying that. I'm merely saying it's still not enough for some cases, and what I'm suggesting will shore up this gap for very little cost.

> [@bergus](#):
>
> I don't understand, as private fields are not part of the public interface that duck typing tests for.

Don't be confused. The argument isn't about duck-typing in specific, but type identification in general. As has been shown in the many issues in github for the class-fields proposal, a duck-typed object passed to a member function that's expecting the object to contain private fields will throw. There's no way to test for that other than `try/catch`. There's nothing wrong with that. It does, however, show that duck-type checking an object is insufficient to provide any reasonable assurance that a particular method using private fields will be able to use that object.

> [@bergus](#):
>
> You still haven't completely described what you mean by "well defined". Yes, if you don't trust others to manipulate your object without breaking your contracts, you'll have to seal it. But how does introducing `Symbol.classType` help with that?

What I'm proposing is not simply the Symbol itself, but also the engine modification to make it happen automatically. It boils down to this:

1. the engine creates an ES Object instance
2. if new.target is a Function, then define `[Symbol.classType] = new.target` on the new object.
3. else define `[Symbol.classType] = null` on the new object
4. mark the property non-configurable, non-enumerable, non-writable

The process above happens the instant the engine creates a new ES Object. I'm assuming that the creation of a native object also causes the instantiation of an ES Object. I don't know much about engine internals.

With this, we can know both the name and constructor for any given instance constructed via `new`. This means the weaknesses you attribute don't exist.

1. Unlike `instanceof`, the value is always available and unassailable.
2. While we wouldn't be able to compare by function, we could still compare by name, as in: `obj[Symbol.classType] == "SomeKnownConstructorName"`. Still not flawless since multiple classes can have the same name, but there's another benefit to shore that up.
3. Since `.constructor` is mutable, it is unreliable. Also, since the prototype can be replaced at any time unless the object has been sealed, doubly so.

About that other benefit: since the constructing function is always available via the Symbol, its `.prototype` can be duck-typed to verify we're working with the desired object. This also gives us the benefit of being able to check and see if the object's prototype object has been replaced as well as the ability to call the methods on the original prototype if need be.

From my standpoint, this is a lot of gain for very little cost and 0 interference with existing code or design practices.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 20, 2020, 3:43pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/9 "2020-07-20T15:43:37Z")

</div>

Even if it was immutable, a Proxy could always return any value it wanted at that symbol, and i could also stick it onto any object or function (those are examples of “forging”), which defeats the robustness you’re describing.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 20, 2020, 6:01pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/10 "2020-07-20T18:01:22Z")

</div>

> [@ljharb](#):
>
> a Proxy could always return any value it wanted at that symbol

Much as private fields is ignoring issues with Proxy, so am I, and under the same kind of argument. A Proxy is designed with the intent to lie about its target. If it couldn't, then the Membrane usage (the primary intended usage for Proxy) wouldn't be possible.

> [@ljharb](#):
>
> and i could also stick it onto any object or function (those are examples of “forging”), which defeats the robustness you’re describing.

I think you missed something here. Every object, whether created by `new` or not would have this property added by the engine immediately after the new object is created and (maybe I neglected to mention this part) before any other property can be added. In this way, no, you couldn't just stick it onto any object or function because it would already be there, unconfigurable, and read-only, with an appropriate value.

It could also be that I typed something other than what I intended.... One of those days today.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 20, 2020, 6:47pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/11 "2020-07-20T18:47:06Z")

</div>

In that case, what you're describing is not a Symbol, it's an internal slot that all objects have, which would mean it needs either syntax, or an API method, to report the value of it.

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [July 20, 2020, 6:50pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/12 "2020-07-20T18:50:19Z")

</div>

> [@rdking](#):
>
> else define `[Symbol.classType] = null` on the new object

> [@rdking](#):
>
> Every object, whether created by `new` or not would have this property added by the engine immediately after the new object is created and (maybe I neglected to mention this part) before any other property can be added.

Ah, that was presented differently before, but now it's becoming clear why you need this to be built into the engine instead of just sticking a custom symbol property on your special objects.

However, we can still do

```javascript
Object.assign(Object.setPrototypeOf(new YourObject), MyObject.prototype), myProperties)

```

to fool code that expects a `YourObject`, and even if your constructor did seal or freeze the instance, we can fake it through

```javascript
Reflect.construct(Object, [], YourObject)

```

which constructs an empty object with the `YourObject` constructor in the symbol but without calling your code.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 20, 2020, 8:06pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/13 "2020-07-20T20:06:38Z")

</div>

> [@ljharb](#):
>
> In that case, what you're describing is not a Symbol, it's an internal slot that all objects have, which would mean it needs either syntax, or an API method, to report the value of it.

Maybe, but I chose doing this with a Symbol because of Proxy and TC39's desire to not modify Proxy to support new features using internal slots without making Proxy work for all internal slots.

> [@bergus](#):
>
> However, we can still do
> 
> ```javascript
> Object.assign(Object.setPrototypeOf(new YourObject, MyObject.prototype), myProperties)
> 
> ```
> 
> to fool code that expects a `YourObject`

Um, no. This scenario is protected against as the `YourObject` instance is still signed properly, still has all the expected private fields, and still has access to the proper prototype through `Symbol.classType.prototype`. So it's still perfectly possible to interact with the object as a valid (albeit possibly corrupted) instance of `YourObject`.

> [@bergus](#):
>
> and even if your constructor did seal or freeze the instance, we can fake it through
> 
> ```javascript
> Reflect.construct(Object, [], YourObject)
> 
> ```
> 
> which constructs an empty object with the `YourObject` constructor in the symbol but without calling your code.

Ok. That one works. the best solution against this that I can think of is to pass the constructor function as a parameter along the internal call chain and use that instead of `new.target` to set the value of the Symbol.

In that way, there's no dependency on `new.target`, and this attack becomes ineffective.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 20, 2020, 8:49pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/14 "2020-07-20T20:49:44Z")

</div>

The other problem is that SES, encapsulation, and polyfilling requirements all would prevent this from being robust in the way you want. In other words, let's say we added a "Pizza" builtin to ES after this `fancyTypeof` operator was added - it'd be very important that `fancyTypeof PizzaPollyfill` returned "Pizza".

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 21, 2020, 3:59pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/15 "2020-07-21T15:59:22Z")

</div>

I'm not concerned about the robustness of a polyfill. By definition, it cannot be as robust as the in-engine solution. The same is true of private fields, the robustness provided by the in-engine solution is only approximated by things like the Babel implementation.

I've reviewed documentation on SES and found that none of its tenets conflict with the goal or functionality of this proposal. If that were the case, then SES would naturally run afoul of any object created that keeps a reference to its own creating function. If you could elaborate on how you think there would be an issue with SES, I'll address it.

I don't have any clue as to why encapsulation would cause an issue with an object having a public, read-only, non-configurable, non-enumerable, Symbol own property that references the constructor function. Please elaborate.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 21, 2020, 5:25pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/16 "2020-07-21T17:25:53Z")

</div>

I'm saying that polyfilling requirements _for everything else_ would mean that this feature, natively, could not be robust in the way you want.

SES works because it includes a membrane implementation, and because it wraps objects in a Proxy, as well as the builtins that would otherwise check internal slots. If you can't use a Proxy, or some other means, to "fake" the tag you're asking about, then it would indeed break SES.

Similarly, if your tag thing landed, and then I was unable to accurately polyfill a _different_ brand new builtin (let alone, all new future builtins) because I was unable to fake the tag, then the ability to polyfill _anything_ would be broken.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 21, 2020, 6:57pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/17 "2020-07-21T18:57:57Z")

</div>

> [@ljharb](#):
>
> SES works because it includes a membrane implementation, and because it wraps objects in a Proxy, as well as the builtins that would otherwise check internal slots. If you can't use a Proxy, or some other means, to "fake" the tag you're asking about, then it would indeed break SES.

This is not an issue. A Proxy is not an ES Object, but rather an exotic object with an identity of its own but no properties of its own. As such, it cannot have a distinct `Symbol.classType` of its own. There's nothing I'm aware of preventing Proxy from lying about the value of this property. Avoiding interference with Proxy is one of the reasons I opted against an internal slot.

> [@ljharb](#):
>
> Similarly, if your tag thing landed, and then I was unable to accurately polyfill a _different_ brand new builtin (let alone, all new future builtins) because I was unable to fake the tag, then the ability to polyfill _anything_ would be broken.

To my knowledge, polyfilling a new built-in object constructor could simply be done using either a `class` definition or a factory that creates the instance using `Reflect.construct`. That's one of the benefits of using a regular property over an internal slot. They're easy to work with.

> [@ljharb](#):
>
> I'm saying that polyfilling requirements _for everything else_ would mean that this feature, natively, could not be robust in the way you want.

I'm assuming that the 2 other responses were examples of the "everything else" that you mentioned. However, those are not issues. Remember, this is just another property on the object. Use of `new` or `Reflect.construct` is all that it takes to set it's value to something other than `null`. That means there is no risk of some other proposal having a feature that somehow interferes with this value unless it is designed into the proposal to do so.

If you think of a case where I'm mistaken, please point it out so I can address it.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [July 21, 2020, 10:37pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/18 "2020-07-21T22:37:56Z")

</div>

I think we're not communicating clearly :-)

Let's imagine two worlds, in which `classType` is a function that returns the class type (so we can set aside the specific mechanics for this discussion):

1. There's a future builtin called "Box". It doesn't exist yet, but your "class type" feature does. I polyfill `Box` by installing it onto `globalThis.Box`. What is `classType(new Box())`?

2. Much later, there's now an existing builtin called "Box". As such, my polyfill detects that it doesn't need to do anything. What is `classType(new Box())`?

If the two answers must be different, then `classType` makes all things impossible to faithfully polyfill, because "it's a polyfill" can be detected via `classType(polyfill)`.

If the two answers can be the same, then in "world 2", anybody could make a class (or other object) at any time for which `classType(fakeThing) === classType(new Box())`, which makes the feature identically as robust (or not robust) as `instanceof`.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 22, 2020, 8:02pm UTC](https://es.discourse.group/t/new-well-known-symbol-symbol-classtype/399/19 "2020-07-22T20:02:24Z")

</div>

> [@ljharb](#):
>
> There's a future builtin called "Box". It doesn't exist yet, but your "class type" feature does. I polyfill `Box` by installing it onto `globalThis.Box` . What is `classType(new Box())` ?

`new Box()[Symbol.classType] === Box` where `Box === globalThis.Box`

> [@ljharb](#):
>
> Much later, there's now an existing builtin called "Box". As such, my polyfill detects that it doesn't need to do anything. What is `classType(new Box())` ?

`new Box()[Symbol.classType] === Box` where `Box === [native function] Box`

> [@ljharb](#):
>
> If the two answers must be different, then `classType` makes all things impossible to faithfully polyfill, because "it's a polyfill" can be detected via `classType(polyfill)` .

The two answers are both the same and different concurrently. Within the running engine context of either scenario, the answer is consistent. That's all that counts. The particular `Box` that created the instance is the value of the Symbol property. I get the problem you're trying to point out. However, that's not an issue at all as it only matters that the instance knows its constructor during a particular run of the engine.
