0
class Hello {
  hi() {
    return 'hi'
  }
}

const hello = new Hello()

console.log(Object.getPrototypeOf(hello))

// {}

however, i use tsc to compile to javascript and run, the output is the following:

var Hello = /** @class */ (function () {
    function Hello() {
    }
    Hello.prototype.hi = function () {
        return 'hi';
    };
    return Hello;
}());
var hello = new Hello();
console.log(Object.getPrototypeOf(hello));
// { hi: [Function (anonymous)] }

why they behave differently?

kurt
  • 91
  • 7
  • 1
    They don't behave differently. They behave exactly the same. Outside cases that really shouldn't need consideration, that is. They are just different constructs that end up in the same concept. The transformation is equivalent (again, sans cases which aren't even relevant here). – VLAZ Mar 23 '23 at 22:37
  • @VLAZ thanks! but why the getPrototypeOf return differently? – kurt Mar 23 '23 at 22:50
  • Printed differently by the console. It's two different constructs. Again, they end up doing the same thing. `2+2` and `2*2` also look different. But share a result. – VLAZ Mar 23 '23 at 22:51
  • When you have an emit target that doesn't support classes, prototypes are emitted instead. `class` vs `prototype` has nothing to do with Typescript. So it seems like the actual question here is "why does Object.getPrototypeOf(hello) return an empty object?" To which I think this is the answer: https://stackoverflow.com/questions/36419713/are-es6-classes-just-syntactic-sugar-for-the-prototypal-pattern-in-javascript which says "ES6 classes are not just syntactic sugar for the prototypal pattern." – Alex Wayne Mar 23 '23 at 22:52
  • @AlexWayne it's *not* an empty object though. – VLAZ Mar 23 '23 at 22:58
  • @VLAZ Well, I should have said "why does Object.getPrototypeOf(hello) return something different when printed to the console when using classes vs prototypes?" – Alex Wayne Mar 23 '23 at 23:05

1 Answers1

2

why they behave differently?

They do not behave differently. The two declarations are essentially equivalent. The differences are not worth considering for these two pieces of code.

class HelloClass {
  hi() {
    return 'hi'
  }
}

var HelloFunction = /** @class */ (function () {
    function Hello() {
    }
    Hello.prototype.hi = function () {
        return 'hi';
    };
    return Hello;
}());

const hello1 = new HelloClass()
var hello2 = new HelloFunction();

const proto1 = Object.getPrototypeOf(hello1);
const proto2 = Object.getPrototypeOf(hello2);


console.log("hi" in proto1);
console.log("hi" in proto2);

console.log(proto1.hasOwnProperty("hi"));
console.log(proto2.hasOwnProperty("hi"));

console.log(hello1.hi());
console.log(hello2.hi());

The only reason the class prototype will not show the hi method is because class methods are not set as enumerable:

class HelloClass {
  hi() {
    return 'hi'
  }
}

var HelloFunction = /** @class */ (function () {
    function Hello() {
    }
    Hello.prototype.hi = function () {
        return 'hi';
    };
    return Hello;
}());

const hello1 = new HelloClass()
var hello2 = new HelloFunction();

const proto1 = Object.getPrototypeOf(hello1);
const proto2 = Object.getPrototypeOf(hello2);

const hi1 = Object.getOwnPropertyDescriptor(proto1, "hi");
const hi2 = Object.getOwnPropertyDescriptor(proto2, "hi");

console.log(hi1);
console.log(hi2);

console.log(proto1);
console.log(proto2);

Which should make no actual difference in practice. Especially in TypeScript where it would not be type-safe to do something like for(const prop in obj) while in JavaScript this is almost entirely useless to invoke on a class instance. It would probably be classified as "uncommon practice", yet I have yet to see it practised. There is probably some code somewhere that tries it hence the "uncommon" part.

For "fuller equivalency" the ES5 code can be changed to use Object.defineProperty:

class HelloClass {
  hi() {
    return 'hi'
  }
}

var HelloFunction = /** @class */ (function () {
    function Hello() {
    }
    Object.defineProperty(Hello.prototype, "hi", {
      value: function hi() { //name the function
        return 'hi';
      },
      enumerable: false, //make it non-enumerable
      writable: true,
      configurable: true
    });
    return Hello;
}());

const hello1 = new HelloClass()
var hello2 = new HelloFunction();

const proto1 = Object.getPrototypeOf(hello1);
const proto2 = Object.getPrototypeOf(hello2);

const hi1 = Object.getOwnPropertyDescriptor(proto1, "hi");
const hi2 = Object.getOwnPropertyDescriptor(proto2, "hi");

console.log(hi1);
console.log(hi2);

console.log(proto1);
console.log(proto2);

However, chances of this having any impact is minimal. Again, especially in the context of TypeScript where the compiler will keep you from trying to do type-unsafe property enumeration of class instances:

class Hello {
  hi() {
    return 'hi'
  }
}

const hello = new Hello()

for (const prop in hello) {
  hello[prop]; //compilation error for indexing with string property
}

Playground Link

By the time you go around this compilation error and manage to produce code which might be influenced by whether a method is enumerable or not, you would actually end up with code you should do use anyway as there are better alternatives.

VLAZ
  • 26,331
  • 9
  • 49
  • 67