Search code examples
javascriptoopprototypeprototype-chain

JavaScript Error properties are different in an instance and its prototype - how so?


I can't make sense of how Error properties are defined. Some are defined on the instance, some are defined on the prototype. Shouldn't it be impossible? I mean, I would expect that a freshly created instance would have exactly the same properties as the prototype. Here's what I mean:

let err = new Error('Test');

Object.getOwnPropertyNames(err);
// Array(4) [ "fileName", "lineNumber", "columnNumber", "message" ]

Object.getOwnPropertyNames(Object.getPrototypeOf(err));
// Array(5) [ "toString", "message", "name", "stack", "constructor" ]

How is it possible that fileName, lineNumber, columnNumber exist on the instance, but do not exist on the prototype if it the instance just been created and not altered in any way?

Also, the instance does not have stack as its own property, is it because it's non-enumerable?


Solution

  • How is it possible that fileName, lineNumber, columnNumber exist on the instance, but do not exist on the prototype if it the instance just been created and not altered in any way?

    There is nothing magical going on here. Error is a constructor function. That function can create any properties on the instance.

    Example:

    class Error {
      constructor() {
        this.fileName = '<script>';
      }
    }
    
    const err = new Error();
    
    console.log(Object.getOwnPropertyNames(err));
    console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(err)));

    As you can see, fileName is a property that only exists on the instance.

    Shouldn't it be impossible? I mean, I would expect that a freshly created instance would have exactly the same properties as the prototype.

    That's not how it's supposed to work. The prototype contains properties that should be shared among all instances, and the instance contains properties specific to that instance. In the end, a prototype is just an object pointed to by another object (the instance). It is only used when accessing a property on the instance. If that property doesn't exist on the instance, the JS runtime will look it up on the instance's prototype, etc. Nothing more, nothing less.

    Maybe you are surprised to see message on the instance and the prototype. The message property on the prototype is the default error message. It's used if the error instance doesn't have an own message (if you don't pass an argument to the Error constructor the error instance does not have an own message property (see the spec)).

    const err = new Error();
    console.log(Object.getOwnPropertyNames(err));
    console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(err)));

    Also, the instance does not have stack as its own property, is it because it's non-enumerable?

    No, an "own" property is an "own" property regardless of its enumerability. stack is a getter/setter and thus it can operate on the current instance via this (or however that functionality is implemented for native functions).

    console.log(Object.getOwnPropertyDescriptor(Error.prototype, 'stack'));
    // Object { get: stack(), set: stack(), enumerable: false, configurable: true }