[英]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.