简体   繁体   中英

If JavaScript doesn't support Classical inheritance why am I able to create constructors and use new keyword?

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

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 .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM