简体   繁体   English

如果JavaScript不支持经典继承,为什么我能够创建构造函数并使用新关键字?

[英]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. 根据MDN javascript只支持原型继承。 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: 什么更令人困惑,我可以使用.prototype关键字向构造函数添加一个方法:

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: 然而,有一种使用Object.Create创建对象的正确方法,这显然是基于原型的对象创建:

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. Human是一个包含prototype对象的函数。 Instances of Human extend that prototype object with their own data initialized in the Human constructor. Human实例扩展了原型对象,并在Human构造函数中初始化了自己的数据。
  • The prototype object can be modified at runtime. 可以在运行时修改prototype对象。 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. 即使在创建了类的实例之后,您仍然可以通过在prototype对象上添加或更改属性来修改其继承的行为。 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. 在原型继承中,类只是一个可构造函数的对象,这意味着它可以使用new关键字调用,但除此之外,可以像任何其他对象一样对待。

Given this information, let's demonstrate a few key similarities and differences between Object.create() and new : 鉴于此信息,让我们演示Object.create()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 : 它一开始可能看起来不一样,但这两个片段实际上是创建一个具有完全相同布局的实例josh

{
  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 . 这展示了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 . 它是继承的路径,决定了对象的行为,并表明josh继承自Human ,它继承自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 . 上面两个Stack Snippet控制台之间的区别是由于第一个片段的constructorHuman.prototype不可枚举属性,而第二个片段的constructorhuman可枚举属性。

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. 如果你想拆开第二个片段,我强烈建议你仔细看看MDN上Object.create()的文档,如果你能学习密集的语言,甚至可能会看一下它的规范。

Here's how you can use Object.create() with our definition of Human instead: 以下是如何将Object.create()与我们对Human的定义一起使用:

 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). 这通过使用josh作为上下文( this关键字) 调用 ES5构造函数来初始化实例josh的实例属性。

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: 最后,由于在评论中提到过,所有这些都可以使用ES6 class关键字进行抽象,这仍然使用原型继承:

 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. 输出可能看起来不同但是如果你检查真正的开发者控制台,你会发现josh布局的唯一区别是由于ES6类声明像walk()这样的成员方法作为Human.prototype 非可枚举属性Human.prototype ,这就是为什么它不会出现在Stack Snippet控制台中。

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 ): 你不能像在ES5中演示的那样使用Object.create() ,因为ES6 class只能构造 (可以使用new调用)而不能调用 (不使用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() . 我试图想出一种更容易在Stack Snippet控制台中看到对象原型链的方法,所以我写了这个函数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. 在最后两个输出中, class HumanES6 { ... }实际上是指类声明中的constructor函数。 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 . 由于从未调用constructor来初始化josh实例,因此最后一个输出没有自己的属性eyesfeet

You can use new because the language specification defined it that way. 您可以使用new因为语言规范以这种方式定义它。 The creators of JavaScript could also have omitted the possibility of using the new keyword or Object.create() . JavaScript的创建者也可能忽略了使用new关键字或Object.create()的可能性。


new does not in and of itself suggest anything about inheritance; new本身并没有提出有关继承的任何建议; 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. 它恰好是在JavaScript中创建新“对象”的关键字。

And depending on the language, new has different meanings. 根据语言的不同, new有不同的含义。 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. 基于经典继承的语言可以在没有new关键字的情况下工作。 Or it could have a deprecated new keyword in favor of a better way to create objects in a newer version of the language. 或者它可能有一个已弃用的new关键字,支持更好的方法来在较新版本的语言中创建对象。

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. 因此,按照规范给出的简单接受通常是有帮助的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 理解为什么真正的原型继承优于经典/伪原型继承以及为什么我不应该使用“新” - Understanding why true prototypal inheritance is better than classical/pseudo prototypal inheritance and why i shouldn't use “new” 在ECMA-262或ECMA 5.1之前的Javascript是否不具有伪古典继承或原型继承支持,这是真的吗? - Is it true that Javascript, before ECMA-262 or ECMA 5.1, doesn't have pseudo-classical inheritance or prototypal inheritance support? ECMAScript 6是否鼓励在JavaScript中使用经典继承? - Does ECMAScript 6 encourage the use of classical inheritance in JavaScript? 为什么函数在构造函数中声明时不使用new关键字? - Why functions don't use new keyword while being declared in constructors? 理解Javascript中的经典继承 - Understanding Classical Inheritance in Javascript ES6类语法不为我们提供“经典”继承吗? - Doesn't ES6 Class Syntax provide us with “classical” inheritance? 当一个函数表现得像一个类但不使用class关键字,也不使用“new”关键字(在Javascript中)时会调用什么? - What is it called when a function behaves like a class but doesn't use the class keyword, nor “new” keyword (in Javascript)? JavaScript inheritance 没有“新”关键字 - JavaScript inheritance without the 'new' keyword Javascript经典继承调用父 - Javascript Classical Inheritance calling parent JavaScript中的经典继承:重新分配构造函数 - Classical Inheritance in JavaScript : Reassigning the constructor
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM