繁体   English   中英

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

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

根据MDN javascript只支持原型继承。 但我可以做到以下几点:

function Human() {

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

}

Var Mark = new Human();

什么更令人困惑,我可以使用.prototype关键字向构造函数添加一个方法:

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

然而,有一种使用Object.Create创建对象的正确方法,这显然是基于原型的对象创建:

var Human = {
  eyes: 2,
  feets: 2

}

var Mark = Object.create(Human);

有人可以帮我清楚一下吗? 谢谢

您应该首先理解的是,您提供的代码片段仍然是原型继承,这就是原因:

  • Human是一个包含prototype对象的函数。 Human实例扩展了原型对象,并在Human构造函数中初始化了自己的数据。
  • 可以在运行时修改prototype对象。 即使在创建了类的实例之后,您仍然可以通过在prototype对象上添加或更改属性来修改其继承的行为。 经典继承不可能实现这一点。
  • 在经典继承中,类和对象之间存在明显差异。 在原型继承中,类只是一个可构造函数的对象,这意味着它可以使用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); 

它一开始可能看起来不一样,但这两个片段实际上是创建一个具有完全相同布局的实例josh

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

也就是说:

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

这展示了josh原型链 它是继承的路径,决定了对象的行为,并表明josh继承自Human ,它继承自Object

上面两个Stack Snippet控制台之间的区别是由于第一个片段的constructorHuman.prototype不可枚举属性,而第二个片段的constructorhuman可枚举属性。

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

以下是如何将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); 

这通过使用josh作为上下文( this关键字) 调用 ES5构造函数来初始化实例josh的实例属性。

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

 class Human { constructor() { this.eyes = 2; this.feet = 2; } walk() { } } var josh = new Human(); console.log(josh); 

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

你不能像在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 

附录

我试图想出一种更容易在Stack Snippet控制台中看到对象原型链的方法,所以我写了这个函数layout() 它可以递归到对象的原型链中,并使所有属性都可以枚举。 由于原型链不能有循环,因此永远不会陷入无限递归:

 // 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} 

这里有两点需要注意。

  • 在最后两个输出中, class HumanES6 { ... }实际上是指类声明中的constructor函数。 在原型继承中,类及其构造函数是同义词。
  • 由于从未调用constructor来初始化josh实例,因此最后一个输出没有自己的属性eyesfeet

您可以使用new因为语言规范以这种方式定义它。 JavaScript的创建者也可能忽略了使用new关键字或Object.create()的可能性。


new本身并没有提出有关继承的任何建议; 它也可以存在于没有继承的语言中。 它恰好是在JavaScript中创建新“对象”的关键字。

根据语言的不同, new有不同的含义。 它可以只定义新对象的创建,但也可以包括在何处/如何分配内存的含义,和/或负责内存生命周期的内容。

基于经典继承的语言可以在没有new关键字的情况下工作。 或者它可能有一个已弃用的new关键字,支持更好的方法来在较新版本的语言中创建对象。

您可以设想从描述符创建新对象的各种方法:

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

所有这些在不同语言中的含义可能略有不同。 因此,如果一种语言借用另一种语言的关键词或概念,你就不应该费心,因为大多数时候它们在次要(甚至是关键)细节上都有所不同。

因此,请使用您以前的知识来帮助学习新语言,但不要太努力,以便在不同语言之间完美地同义这些概念。 您必须记住,即使它们看起来相似,其他语言也有不同的概念。 因此,按照规范给出的简单接受通常是有帮助的。

暂无
暂无

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

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