簡體   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