[英]Understanding why true prototypal inheritance is better than classical/pseudo prototypal inheritance and why i shouldn't use “new”
[英]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控制台之间的区别是由于第一个片段的constructor
是Human.prototype
的不可枚举属性,而第二个片段的constructor
是human
的可枚举属性。
如果你想拆开第二个片段,我强烈建议你仔细看看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
实例,因此最后一个输出没有自己的属性eyes
和feet
。 您可以使用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.