首頁 web前端 js教程 深入了解JavaScript中的原型和繼承

深入了解JavaScript中的原型和繼承

Nov 26, 2019 pm 06:11 PM
javascript 原型 繼承

本文主要講了原型如何在JavaScript中工作,以及如何透過[Prototype]所有物件共享的隱藏屬性連結物件屬性和方法;以及如何建立自訂建構函式以及原型繼承如何運作以傳遞屬性和方法值。

深入了解JavaScript中的原型和繼承

介紹

JavaScript是一種基於原型的語言,這意味著物件屬性和方法可以透過具有克隆和擴展能力的通用物件共享。這被稱為原型繼承,與類別繼承不同。在流行的物件導向程式語言中,JavaScript是相對獨特的,因為其他著名的語言,如PHP、Python和Java都是基於類別的語言,它們將類別定義為物件的藍圖。

【相關課程推薦:JavaScript影片教學

在文中,我們將學習什麼是物件原型,以及如何使用建構函式將原型擴展為新物件。我們還將學習繼承和原型鏈。

JavaScript原型

JavaScript中的每個物件都有一個稱為[[Prototype]]的內部屬性。我們可以透過建立一個新的空物件來演示這一點。

let x = {};
登入後複製

這是我們通常創建物件的方法,但是請注意,另一種實作方法是使用物件建構子:

let x = new object()
登入後複製

包圍[[Prototype]]的雙方括號表示它是一個內部屬性,不能在程式碼中直接存取。

要找到這個新建立物件的[[Prototype]],我們將使用getPrototypeOf()方法。

Object.getPrototypeOf(x);
登入後複製

輸出將由幾個內建屬性和方法組成。

輸出:

{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
登入後複製
登入後複製
登入後複製

找到的另一種方法[[Prototype]]是透過__proto__財產。 __proto__是一個公開[[Prototype]]物件內部的屬性。

要注意的是,. _proto__是一個遺留特性,不應該在生產程式碼中使用,而且它也不是在每個現代瀏覽器中都存在。但是,我們可以在本文中使用它來進行演示。

x.__proto__;
登入後複製

輸出將與使用getPrototypeOf()相同。

輸出

{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
登入後複製
登入後複製
登入後複製

重要的是JavaScript中的每個物件都有一個[[Prototype]],因為它為任何兩個或多個物件建立了連結的方法。

您建立的物件和內建物件(如Date和Array)一樣具有[[Prototype]]。可以透過prototype屬性將這個內部屬性從一個物件引用到另一個對象,我們將在本教學的後面看到這一點。

原型繼承

當您試圖存取物件的屬性或方法時,JavaScript將首先搜尋物件本身,如果沒有找到,它將搜尋物件的[[Prototype]]。如果在查詢物件及其[[Prototype]]後仍然沒有找到匹配項,JavaScript將檢查被連結物件的原型,並繼續搜尋,直到到達原型鏈的末端。

原型鏈的結尾是Object.prototype。所有物件都繼承物件的屬性和方法。任何超出鏈末端的搜尋都會導致null。

在我們的範例中,x是一個從object繼承而來的空物件。 x可以使用物件具有的任何屬性或方法,例如toString()。

x.toString();
登入後複製

輸出

[object Object]
登入後複製

這個原型鏈只有一個鍊長。 x - > Object。我們知道這一點,因為如果我們試圖將兩個[[Prototype]]屬性連結在一起,它將為null。

x.__proto__.__proto__;
登入後複製

輸出

null
登入後複製

讓我們看看另一種類型的物件。如果您有使用JavaScript處理陣列的經驗,您就會知道它們有許多內建方法,例如pop()和push()。建立新陣列時可以存取這些方法的原因是,建立的任何陣列都可以存取array .prototype上的屬性和方法。

我們可以透過建立一個新的陣列來測試它。

let y = [];
登入後複製

請記住,我們也可以把它寫成陣列建構函數,讓y = new array()。

如果我們查看新y數組的[[Prototype]],我們將看到它比x物件具有更多的屬性和方法。它繼承了Array.prototype中的所有內容。

y.__proto__;
登入後複製
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]
登入後複製

您將注意到原型上的建構子屬性被設定為Array()。建構函數屬性傳回物件的建構函數,這是一種用於從函數建構物件的機制。

我們現在可以將兩個原型連結在一起,因為在這種情況下,我們的原型鏈更長。它看起來像y -> Array -> Object。

y.__proto__.__proto__;
登入後複製
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}
登入後複製
登入後複製
登入後複製

這個鏈現在引用Object.prototype。我們可以根據建構函式的Prototype屬性來測試內部的[[Prototype]],以確定它們所引用的是相同的東西。

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true
登入後複製

我們也可以使用isPrototypeOf()方法來實現這一點。

Array.prototype.isPrototypeOf(y);      // true
Object.prototype.isPrototypeOf(Array); // true
登入後複製

我们可以使用instanceof操作符来测试构造函数的prototype属性是否出现在对象原型链中的任何位置。

y instanceof Array; // true
登入後複製

总而言之,所有JavaScript对象都具有隐藏的内部[[Prototype]]属性(可能__proto__在某些浏览器中公开)。对象可以扩展,并将继承[[Prototype]]其构造函数的属性和方法。

这些原型可以被链接,并且每个额外的对象将继承整个链中的所有内容。链以Object.prototype结束。

构造器函数

构造函数是用来构造新对象的函数。new操作符用于基于构造函数创建新实例。我们已经看到了一些内置的JavaScript构造函数,比如new Array()和new Date(),但是我们也可以创建自己的自定义模板来构建新对象。

例如,我们正在创建一个非常简单的基于文本的角色扮演游戏。用户可以选择一个角色,然后选择他们将拥有的角色类别,例如战士、治疗者、小偷等等。

由于每个字符将共享许多特征,例如具有名称、级别和生命值,因此创建构造函数作为模板是有意义的。然而,由于每个角色类可能有非常不同的能力,我们希望确保每个角色只能访问自己的能力。让我们看看如何使用原型继承和构造函数来实现这一点。

首先,构造函数只是一个普通函数。当使用new关键字的实例调用它时,它将成为一个构造函数。在JavaScript中,我们按照惯例将构造函数的第一个字母大写。

// Initialize a constructor function for a new Hero
function Hero(name, level) {
  this.name = name;  this.level = level;
}
登入後複製

我们创建了一个名为Hero的构造函数,它有两个参数:name和level。因为每个字符都有一个名称和一个级别,所以每个新字符都有这些属性是有意义的。this关键字将引用创建的新实例,因此将this.name设置为name参数将确保新对象具有name属性集。

现在我们可以用new创建一个新的实例。

let hero1 = new Hero('Bjorn', 1);
登入後複製

如果我们在控制台输出hero1,我们将看到已经创建了一个新对象,其中新属性按预期设置。

输出

Hero {name: "Bjorn", level: 1}
登入後複製

现在,如果我们得到hero1的[[Prototype]],我们将能够看到构造函数Hero()。(记住,它的输入与hero1相同。,但这是正确的方法。)

Object.getPrototypeOf(hero1);
登入後複製

输出

constructor: ƒ Hero(name, level)
登入後複製

您可能注意到,我们只在构造函数中定义了属性,而没有定义方法。在JavaScript中,为了提高效率和代码可读性,通常在原型上定义方法。

我们可以使用prototype向Hero添加一个方法。我们将创建一个greet()方法。

// Add greet method to the Hero prototype
Hero.prototype.greet = function () {
  return `${this.name} says hello.`;
}
登入後複製

因为greet()在Hero的原型中,而hero1是Hero的一个实例,所以这个方法对hero1是可用的。

hero1.greet();
登入後複製
登入後複製
登入後複製

输出

"Bjorn says hello."
登入後複製
登入後複製

如果检查Hero的[[Prototype]],您将看到greet()现在是一个可用选项。

这很好,但是现在我们想要为英雄创建角色类。将每个类的所有功能都放到Hero构造函数中是没有意义的,因为不同的类具有不同的功能。我们希望创建新的构造函数,但也希望它们连接到原始的Hero。

我们可以使用call()方法将属性从一个构造函数复制到另一个构造函数。让我们创建一个战士和一个治疗构造器。

// Initialize Warrior constructor
function Warrior(name, level, weapon) {
  // Chain constructor with call
  Hero.call(this, name, level);  // Add a new property
  this.weapon = weapon;
}// Initialize Healer constructor
function Healer(name, level, spell) {
  Hero.call(this, name, level);  this.spell = spell;
}
登入後複製

两个新的构造函数现在都具有Hero和unqiue的属性。我们将把attack()方法添加到Warrior中,而heal()方法添加到Healer中。

Warrior.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}
登入後複製

此时,我们将使用两个可用的新字符类创建字符。

const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');
登入後複製

hero1现在被认为是拥有新属性的战士。

输出

Warrior {name: "Bjorn", level: 1, weapon: "axe"}
登入後複製

我们可以使用我们在战士原型上设置的新方法。

hero1.attack();
登入後複製
Console
"Bjorn attacks with the axe."
登入後複製

但是如果我们尝试使用原型链下面的方法会发生什么呢?

hero1.greet();
登入後複製
登入後複製
登入後複製

输出

Uncaught TypeError: hero1.greet is not a function
登入後複製

使用call()链接构造函数时,原型属性和方法不会自动链接。我们将使用Object.create()来链接原型,确保在创建并添加到原型的任何其他方法之前将其放置。

Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);
// All other prototype methods added below…
登入後複製

现在我们可以在一个战士或治疗者的实例上成功地使用Hero的原型方法。

hero1.greet();
登入後複製
登入後複製
登入後複製

输出

"Bjorn says hello."
登入後複製
登入後複製

这里是我们的角色创建页面的完整代码。

// Initialize constructor functions
function Hero(name, level) {
  this.name = name;
  this.level = level;
}
 
function Warrior(name, level, weapon) {
  Hero.call(this, name, level);
 
  this.weapon = weapon;
}
 
function Healer(name, level, spell) {
  Hero.call(this, name, level);
 
  this.spell = spell;
}
 
// Link prototypes and add prototype methods
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);
 
Hero.prototype.greet = function () {
  return `${this.name} says hello.`;
}
 
Warrior.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}
 
Healer.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}
 
// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');
登入後複製

使用这段代码,我们已经用基本属性创建了Hero类,从原始构造函数创建了两个名为Warrior和Healer的字符类,向原型添加了方法,并创建了单独的字符实例。

本文來自 js教學 欄目,歡迎學習!

以上是深入了解JavaScript中的原型和繼承的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

C++ 函式繼承詳解:如何在繼承中使用「基底類別指標」和「衍生類別指標」? C++ 函式繼承詳解:如何在繼承中使用「基底類別指標」和「衍生類別指標」? May 01, 2024 pm 10:27 PM

在函數繼承中,使用「基底類別指標」和「衍生類別指標」來理解繼承機制:基底類別指標指向派生類別物件時,執行向上轉型,只存取基底類別成員。派生類別指標指向基底類別物件時,執行向下轉型(不安全),必須謹慎使用。

C++ 中繼承和多態性如何影響類別的耦合度? C++ 中繼承和多態性如何影響類別的耦合度? Jun 05, 2024 pm 02:33 PM

繼承和多態性會影響類別的耦合度:繼承會增加耦合度,因為衍生類別依賴基底類別。多態性可以降低耦合度,因為物件可以透過虛擬函數和基底類別指標以一致的方式回應訊息。最佳實踐包括謹慎使用繼承、定義公共介面、避免在基底類別中新增資料成員,以及透過依賴注入解耦類別。實戰案例顯示如何使用多態性和依賴注入來降低銀行帳戶應用程式中的耦合度。

C++ 函式繼承詳解:如何偵錯繼承中出現的錯誤? C++ 函式繼承詳解:如何偵錯繼承中出現的錯誤? May 02, 2024 am 09:54 AM

繼承錯誤調試技巧:確保正確的繼承關係。使用偵錯器逐步執行程式碼,檢查變數值。確保正確使用virtual修飾符。檢查隱藏的繼承帶來的菱形繼承問題。檢查抽象類別中未實現的純虛函數。

C++ 函式繼承詳解:如何理解繼承中的「is-a」與「has-a」關係? C++ 函式繼承詳解:如何理解繼承中的「is-a」與「has-a」關係? May 02, 2024 am 08:18 AM

C++函式繼承詳解:掌握「is-a」和「has-a」關係什麼是函式繼承?函數繼承是C++中一種將衍生類別中定義的方法與基底類別中定義的方法關聯起來的技術。它允許衍生類別存取和重寫基底類別的方法,從而擴展了基底類別的功能。 「is-a」和「has-a」關係在函數繼承中,「is-a」關係指派生類別是基底類別的子類型,也就是說,衍生類別「繼承」了基底類別的特性和行為。 「has-a」關係指派生類別包含對基底類別物件的參考或指針,也就是說,衍生類別「擁有」了基底類別物件。語法以下是如何實作函數繼承的語法:classDerivedClass:pu

原神4.4版新地圖介紹 原神4.4版新地圖介紹 Jan 31, 2024 pm 06:36 PM

原神4.4版新地圖介紹,小夥伴們原神這次4.4版本也是迎來了璃月的海燈節,同時將在4.4版本推出一個新的地圖區域,名為沉玉谷。根據提供的信息,沉玉谷實際上是翹英莊的一部分,但玩家更習慣稱其為沉玉谷。下面就讓小編來跟大家介紹一下新地圖吧。原神4.4版新地圖介紹4.4版本將開放璃月北部的「沉玉谷·上谷」、「沉玉谷·南陵」和「來歆山」,在「沉玉谷·上谷」已為旅行者開啟傳送錨點。 ※完成魔神任務序章·第三幕龍與自由之歌」後,將自動解鎖該傳送錨點。二、翹英莊當春日溫煦的柔風再度撫過沉玉的山野,那馥鬱的

'PHP物件導向程式設計入門:從概念到實踐” 'PHP物件導向程式設計入門:從概念到實踐” Feb 25, 2024 pm 09:04 PM

什麼是物件導向程式設計?物件導向程式設計(OOP)是一種程式設計範式,它將現實世界中的實體抽象化為類,並使用物件來表示這些實體。類別定義了物件的屬性和行為,而物件則實例化了類別。 OOP的主要優點在於它可以使程式碼更易於理解、維護和重複使用。 OOP的基本概念OOP的主要概念包括類別、物件、屬性和方法。類別是物件的藍圖,它定義了物件的屬性和行為。物件是類別的實例,它具有類別的所有屬性和行為。屬性是物件的特徵,它可以儲存資料。方法是物件的函數,它可以對物件的資料進行操作。 OOP的優點OOP的主要優點包括:可重複使用性:OOP可以讓程式碼更

C++ 函式繼承詳解:什麼時候不該使用繼承? C++ 函式繼承詳解:什麼時候不該使用繼承? May 04, 2024 pm 12:18 PM

在以下情況下不應使用C++函數繼承:衍生類別需要不同實作時,應建立具有不同實作的新函數。衍生類別不需要函數時,應宣告為一個空類別或使用私有、未實作的基底類別成員函數來停用函數繼承。函數不需要繼承時,應使用其他機制(例如範本)來實作程式碼重用。

Java 介面與抽象類別:通往程式設計天堂之路 Java 介面與抽象類別:通往程式設計天堂之路 Mar 04, 2024 am 09:13 AM

介面:無實作的契約介面在Java中定義了一組方法簽名,但不提供任何具體實作。它充當一種契約,強制實作該介面的類別實現其指定的方法。介面中的方法是抽象方法,沒有方法體。程式碼範例:publicinterfaceAnimal{voideat();voidsleep();}抽象類別:部分實作的藍圖抽象類別是一種父類,它提供了一個部分實現,可以被它的子類別繼承。與介面不同,抽象類別可以包含具體的實作和抽象方法。抽象方法是用abstract關鍵字聲明的,並且必須被子類別覆蓋。程式碼範例:publicabstractcla

See all articles