4

According to MDN javascript only support prototypical inheritance. Yet I can do the following:

function Human() {

  this.eyes = 2;
  this.feet = 2;

}

Var Mark = new Human();

What's even more confusing, I can add a method to the constructor using .prototype keyword:

Human.prototype.walk = function(distance) {
  //code
}

Yet there's a proper way to create objects using Object.Create which is apparently the proper prototype based object creation:

var Human = {
  eyes: 2,
  feets: 2

}

var Mark = Object.create(Human);

Can someone please clear this up for me? Thank you

kshatriiya
  • 209
  • 4
  • 12
  • 2
    These may help: [classical inheritance vs prototypal inheritance in javascript](https://stackoverflow.com/questions/19633762/classical-inheritance-vs-prototypal-inheritance-in-javascript/19640910) and [Classical Vs prototypal inheritance](https://stackoverflow.com/questions/1450582/classical-vs-prototypal-inheritance) – Jonathan Lonowski Nov 28 '18 at 04:02
  • Side note: if this is confusing and you have classical inheritance background, ES6 `classes` hide all this (keyword hide, it's still prototype under the hood). I'd still recommend to learn it at some point. – Solo Nov 28 '18 at 04:06
  • Just keep in mind that everything in JavaScript inheritance patterns behaves like `public virtual` modifiers from other programming languages, and that's kinda what you're stuck with. – Patrick Roberts Nov 28 '18 at 04:10
  • @JonathanLonowski I'm made even more confused by the first link, why is he using Object.create in classical inheritance? – kshatriiya Nov 28 '18 at 04:22
  • By the way, who told you `Object.create()` is the "proper" prototype-based object creation? That doesn't sound right to me at all. – Patrick Roberts Nov 28 '18 at 04:22
  • @PatrickRoberts I'm reading this https://www.competa.com/blog/classical-prototypical-inheritance-javascript/ – kshatriiya Nov 28 '18 at 04:22
  • Help me out ppl, I normally just use a constructor, add methods using .prototype and then new keyword to create objects. I've never used Object.create before – kshatriiya Nov 28 '18 at 04:24
  • In no place does it compare these methods or say which one is better or proper or correct. Check it again if you don't believe me. It's just one way to go about prototypal inheritance that they're making you aware of. – Patrick Roberts Nov 28 '18 at 04:25
  • @PatrickRoberts I'm just wondering what the difference is between the two. Supposedly javascript doesn't support classical inheritance but then why are we able to use class based object creation? It's one of the most asked interview questions, difference between classical and prototype inheritance in javascript. – kshatriiya Nov 28 '18 at 04:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/184365/discussion-between-patrick-roberts-and-kshatriiya). – Patrick Roberts Nov 28 '18 at 04:28
  • @kshatriiya I made the mistake of taking the link provided. Not every blog you find on the internet is true or of high quality. This one is bs. I recommend you consult other resources and, if possible, forget having read this one. – traktor Nov 28 '18 at 05:03

2 Answers2

3

The first thing you should understand is that the snippet you've provided as an example is still prototypal inheritance, and here's why:

  • Human is a function which contains a prototype object. Instances of Human extend that prototype object with their own data initialized in the Human constructor.
  • The prototype object can be modified at runtime. Even after you've created instances of the class, you can still modify their inherited behavior by adding or changing properties on the prototype object. None of this is possible with classical inheritance.
  • In classical inheritance, there is a distinct difference between a class and an object. In prototypal inheritance, classes are merely an object that is a constructable function, meaning it can be invoked with the new keyword, but otherwise, can be treated like any other object.

Given this information, let's demonstrate a few key similarities and differences between Object.create() and new:

function Human() {
  this.eyes = 2;
  this.feet = 2;
}

Human.prototype.walk = function () { };

var josh = new Human();

console.log(josh);

var human = {
  constructor: function Human() {
    this.eyes = 2;
    this.feet = 2;
  },
  walk: function () { }
};

// create josh with prototype of human
var josh = Object.create(human);

// initialize own properties by calling constructor
human.constructor.call(josh); // or josh.constructor();

console.log(josh);

It may not look it at first, but these two snippets are actually creating an instance josh with the exact same layout:

{
  eyes: 2,
  feet: 2,
  __proto__: {
    walk: f (),
    constructor: f Human(),
    __proto__: Object.prototype
  }
}

That is to say:

var proto = Object.getPrototypeOf(josh);
var protoProto = Object.getPrototypeOf(proto);

console.log(proto === Human.prototype); // or proto === human
console.log(protoProto === Object.prototype);
<- true
<- true

This demonstrates the prototype chain of josh. It is the path of inheritance that determines the behavior of the object, and shows that josh inherits from Human, which inherits from Object.

The difference between the two Stack Snippet consoles above is due to the fact that the first snippet's constructor is a non-enumerable property of Human.prototype, while the second snippet's constructor is an enumerable property of human.

If you want to pick apart the second snippet, I highly suggest taking a closer look at the documentation for Object.create() on MDN, and possibly even glancing at the specification for it if you can grok the dense language.

Here's how you can use Object.create() with our definition of Human instead:

function Human() {
  this.eyes = 2;
  this.feet = 2;
}

Human.prototype.walk = function () { };

// create prototypal inheritance
var josh = Object.create(Human.prototype);

// initialize own properties
Human.call(josh); // or josh.constructor();

console.log(josh);

This initializes the instance properties of the instance josh by calling the ES5 constructor with josh as the context (the this keyword).

Lastly, since it was mentioned in comments, all of this can be abstracted for simplicity using the ES6 class keyword, which still uses prototypal inheritance:

class Human {
  constructor() {
    this.eyes = 2;
    this.feet = 2;
  }
  
  walk() { }
}

var josh = new Human();

console.log(josh);

The output may appear different but if you check in the real Developer Console, you'll find that the only difference in the layout of josh is due to the fact that ES6 classes declare member methods like walk() as non-enumerable properties of Human.prototype, which is why it doesn't show up in the Stack Snippet console.

You cannot use Object.create() the same way as demonstrated in ES5 because an ES6 class is only constructable (invoke-able with new) and not callable (invoke-able without new):

class Human {
  constructor() {
    this.eyes = 2;
    this.feet = 2;
  }
  
  walk() { }
}

var josh = Object.create(Human.prototype); // still works

// no own properties yet because the constructor has not been invoked
console.log(josh);

// cannot call constructor to initialize own properties
Human.call(josh); // josh.constructor(); would not work either

Addendum

I tried to come up with a way to more easily see the prototype chain of objects in the Stack Snippet console, so I wrote this function layout(). It recurses into an object's prototype chain and makes all the properties enumerable. Since prototype chains cannot have cycles, this can never get stuck in infinite recursion:

// makes all properties in an object's prototype chain enumerable
// don't worry about understanding this implementation
function layout (o) {
  if (typeof o !== 'object' || !o || o === Object.prototype) return o;
  return [...Object.getOwnPropertyNames(o), '__proto__'].reduce(
    (a, p) => Object.assign(a, { [p]: layout(o[p]) }),
    Object.create(null)
  );
}

// this is intentionally one line in order to
// make Stack Snippet Console output readable
function HumanES5() { this.eyes = 2; this.feet = 2; }
HumanES5.prototype.walk = function () { };

var josh = new HumanES5();
console.log(layout(josh));

var josh = Object.create(HumanES5.prototype);
HumanES5.call(josh); // or josh.constructor();
console.log(layout(josh));

class HumanES6 {
  constructor () { this.eyes = 2; this.feet = 2; }
  walk () { }
}

var josh = new HumanES6();
console.log(layout(josh));

var josh = Object.create(HumanES6.prototype);
// HumanES6.call(josh); will fail, remember?
console.log(layout(josh));
.as-console-wrapper{min-height:100%!important}

There are two things to note here.

  • In the last two outputs, class HumanES6 { ... } actually refers to the constructor function in the class declaration. In prototypal inheritance, the class and its constructor are synonymous.
  • The last output doesn't have the own properties eyes and feet since the constructor was never invoked to initialize that instance of josh.
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Oh my god Javascript is a labyrinth but you've shown the way. Thank you very much for going out of your way to make this clear. You're a saint, I'll print this and hang it on my wall. – kshatriiya Nov 28 '18 at 05:46
  • LOL I wouldn't go that far but I'm glad I could help at least. I hope you can now continue to explore these concepts and others on MDN as you delve deeper. – Patrick Roberts Nov 28 '18 at 05:49
1

You can use new because the language specification defined it that way. The creators of JavaScript could also have omitted the possibility of using the new keyword or Object.create().


new does not in and of itself suggest anything about inheritance; it could also exist in languages with no inheritance at all. It just happens to be a keyword to create a new "object" in JavaScript.

And depending on the language, new has different meanings. It could just define the creation of a new object, but could also include the meaning of where/how to allocate the memory, and/or about what is responsible for the memory lifecycle.

A classical inheritance based language could work without a new keyword at all. Or it could have a deprecated new keyword in favor of a better way to create objects in a newer version of the language.

You could conceive various ways of creating a new object from a descriptor:

  new Descriptor(arg1, arg2);
  Descriptor obj(arg1, arg2);
  obj = Descriptor.alloc().init(arg1, arg2);
  obj = Descriptor.new(arg1, arg2);
  obj = create(Descriptor, arg1, arg2);
  ...

All of those could have slightly different meanings in different languages. So you should not bother too much if one language borrows a keyword or concept from another language, because most of the time they differ in minor (or even critical) details.

So use your previous knowledge to aid in learning the new language, but don't try too hard to perfectly synonymize these concepts between different languages. You have to keep in mind that other languages have different concepts even if they look similar. Therefore, it is often helpful to simply accept it as given by the specs.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
t.niese
  • 39,256
  • 9
  • 74
  • 101
  • I personally think that trying to map between languages is a great way to get a deeper understanding of language mechanics. For example when I explored the differences between how a JavaScript `Promise` and a C# `Task` work at the language level (as opposed to the implementation level, which gets pretty hairy especially for C#), it revealed a lot to me that deepened my understanding of one language due to my already existing knowledge of the other. Because of that, I wouldn't necessarily discourage mapping between languages, just discourage making assumptions that they'll be the exact same. – Patrick Roberts Nov 28 '18 at 06:51
  • @PatrickRoberts Yes, that what I actually meant, with taking it as a hint to learn the language features, but not to force the mapping between the languages. – t.niese Nov 28 '18 at 06:57
  • I did upvote your answer because I do like what you're pointing out. I just thought I'd provide a specific example of an insight I gained by this method. Sorry if it sounded like I was critiquing your answer. – Patrick Roberts Nov 28 '18 at 07:04
  • @PatrickRoberts I'm always happy to hear critiquing especially if it is something about wording. Because a sentence doesn't always express what you were going to say. – t.niese Nov 28 '18 at 07:07
  • 1
    I made some edits to your answer. I did not change what you are saying; just how you are saying it. Please feel free to revert if you don't like how it reads now. – Patrick Roberts Nov 28 '18 at 07:18