天天看點

JavaScript【對象原型】

原型是JavaScript對象互相繼承功能的機制。在本文中,我們将解釋什麼是原型,原型鍊如何工作,以及如何設定對象的原型。

先決條件: 了解 JavaScript 函數,熟悉 JavaScript 基礎知識(請參閱第一步​和建構塊​)和 OOJS 基礎知識(請參閱對象簡介)。
目的: 了解 JavaScript 對象原型、原型鍊的工作原理以及如何設定對象的原型。

原型鍊

在浏覽器的控制台中,嘗試建立一個對象文本:

const myObject = {
  city: 'Madrid',
  greet() {
    console.log(`Greetings from ${this.city}`);
  }
}

myObject.greet(); // Greetings from Madrid      

複制到剪貼闆

這是一個具有一個資料屬性 和一個方法的對象。如果在控制台中鍵入對象的名稱,後跟句點,例如 ,則控制台将彈出此對象可用的所有屬性的清單。你會看到以及和,還有很多其他屬性!​

​city​

​​

​greet()​

​​

​myObject.​

​​

​city​

​​

​greet​

__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
toValueOf      

嘗試通路其中之一:

myObject.toString(); // "[object Object]"      

複制到剪貼闆

它有效(即使它不明顯是什麼)。​

​toString()​

這些額外的屬性是什麼,它們來自哪裡?

JavaScript中的每個對象都有一個内置屬性,稱為其原型。原型本身就是一個對象,是以原型将有自己的原型,進而形成所謂的原型鍊。當我們到達一個擁有自己的原型的原型時,鍊條就結束了。​

​null​

注意:指向其原型的對象的屬性不稱為 。它的名稱不是标準的,但實際上所有浏覽器都使用​​​__proto__​​​。通路對象原型的标準方法是 ​​​Object.getPrototypeOf()​​​ 方法。​

​prototype​

當您嘗試通路對象的屬性時:如果在對象本身中找不到該屬性,則會在原型中搜尋該屬性。如果仍然找不到該屬性,則搜尋原型的原型,依此類推,直到找到該屬性或到達鍊的末端,在這種情況下傳回。​

​undefined​

是以,當我們調用 ,浏覽器時:​

​myObject.toString()​

  • 尋找​

    ​toString​

    ​​

    ​myObject​

  • 在那裡找不到它,是以在原型對象中查找​

    ​myObject​

    ​​

    ​toString​

  • 在那裡找到它,并調用它。

原型是什麼?要找出答案,我們可以使用以下函數:​

​myObject​

​​

​Object.getPrototypeOf()​

Object.getPrototypeOf(myObject); // Object {...}      

複制到剪貼闆

這是一個名為 的對象,它是所有對象預設具有的最基本的原型。的原型是 ,是以它位于原型鍊的末尾:​

​Object.prototype​

​​

​Object.prototype​

​​

​null​

對象的原型并不總是 。試試這個:​

​Object.prototype​

const myDate = new Date();
let object = myDate;

do {
  object = Object.getPrototypeOf(object);
  console.log(object);
} while (object);

// Date.prototype
// Object {...}
// null      

複制到剪貼闆

此代碼建立一個對象,然後沿原型鍊向上移動,記錄原型。它向我們表明,的原型是一個對象,而它的原型是 。​

​Date​

​​

​myDate​

​​

​Date.prototype​

​​

​Object.prototype​

實際上,當您調用熟悉的方法(如 )時,您正在調用在 上定義的方法。​

​myDate2.getMonth()​

​​

​Date.prototype​

重影屬性

如果在對象的原型中定義了具有相同名稱的屬性,則在對象中定義了屬性,會發生什麼情況?我看看:

const myDate = new Date(1995, 11, 17);

console.log(myDate.getYear()); // 95

myDate.getYear = function() {
  console.log('something else!')
};

myDate.getYear(); // 'something else!'      

複制到剪貼闆

鑒于原型鍊的描述,這應該是可預測的。當我們調用浏覽器時,首先查找具有該名稱的屬性,并且僅在未定義原型時才檢查原型。是以,當我們添加到 時,将調用 中的版本。​

​getYear()​

​​

​myDate​

​​

​myDate​

​​

​getYear()​

​​

​myDate​

​​

​myDate​

這稱為“陰影”屬性。

設定原型

在 JavaScript 中設定對象原型的方法有很多種,在這裡我們将介紹兩種:和構造函數。​

​Object.create()​

使用 Object.create

該方法将建立一個新對象,并允許您指定将用作新對象原型的對象。​

​Object.create()​

下面是一個示例:

const personPrototype = {
  greet() {
    console.log('hello!');
  }
}

const carl = Object.create(personPrototype);
carl.greet();  // hello!      

複制到剪貼闆

在這裡,我們建立一個對象,它有一個方法。然後,我們使用建立一個新對象作為其原型。現在我們可以調用新對象,原型提供其實作。​

​personPrototype​

​​

​greet()​

​​

​Object.create()​

​​

​personPrototype​

​​

​greet()​

使用構造函數

在 JavaScript 中,所有函數都有一個名為 .将函數作為構造函數調用時,此屬性設定為新構造對象的原型(按照約定,在名為 的屬性中)。​

​prototype​

​​

​__proto__​

是以,如果我們設定構造函數,我們可以確定使用該構造函數建立的所有對象都給定該原型:​

​prototype​

const personPrototype = {
  greet() {
    console.log(`hello, my name is ${this.name}!`);
  }
}

function Person(name) {
  this.name = name;
}

Person.prototype = personPrototype;
Person.prototype.constructor = Person;      

複制到剪貼闆

在這裡,我們建立:

  • 一個對象,它有一個方法​

    ​personPrototype​

    ​​

    ​greet()​

  • 一個構造函數,用于初始化要建立的人的姓名。​

    ​Person()​

然後,我們将函數的屬性設定為指向 。​

​Person​

​​

​prototype​

​​

​personPrototype​

最後一行 () 将原型的屬性設定為用于建立對象的函數。這是必需的,因為在設定屬性後,該屬性指向 的構造函數,而不是(因為被構造為對象文本)。​

​Person.prototype.constructor = Person;​

​​

​constructor​

​​

​Person​

​​

​Person.prototype = personPrototype;​

​​

​personPrototype​

​​

​Object​

​​

​Person​

​​

​personPrototype​

在此代碼之後,使用 建立的對象将作為其原型。​

​Person()​

​​

​personPrototype​

const reuben = new Person('Reuben');
reuben.greet(); // hello, my name is Reuben!      

複制到剪貼闆

這也解釋了為什麼我們之前說過的原型叫做:它是構造函數的屬性。​

​myDate​

​​

​Date.prototype​

​​

​prototype​

​​

​Date​

自己的房産

我們使用上面的構造函數建立的對象有兩個屬性:​

​Person​

  • 一個屬性,它在構造函數中設定,是以它直接出現在對象上​

    ​name​

    ​​

    ​Person​

  • 在原型中設定的方法。​

    ​greet()​

通常可以看到這種模式,其中方法在原型上定義,但資料屬性在構造函數中定義。這是因為對于我們建立的每個對象,方法通常是相同的,而我們通常希望每個對象都有自己的資料屬性值(就像這裡每個人都有不同的名稱一樣)。

直接在對象中定義的屬性(如此處所示)稱為自己的屬性,您可以使用靜态 ​​​Object.hasOwn()​​​ 方法檢查屬性是否為自己的屬性:​

​name​

const irma = new Person('Irma');

console.log(Object.hasOwn(irma, 'name')); // true
console.log(Object.hasOwn(irma, 'greet')); // false      

複制到剪貼闆

注意:您也可以在此處使用非靜态 ​​​Object.hasOwnProperty()​​​ 方法,但我們建議您盡可能使用。​

​Object.hasOwn()​

原型和繼承

原型是JavaScript的一個強大且非常靈活的功能,可以重用代碼群組合對象。

特别是它們支援一個版本的繼承。繼承是面向對象程式設計語言的一個功能,它允許程式員表達這樣一種想法,即系統中的某些對象是其他對象的更專業版本。

例如,如果我們對一所學校進行模組化,我們可能有教授和學生:他們都是人,是以有一些共同的特征(例如,他們都有名字),但每個特征都可能增加額外的特征(例如,教授有一門他們教的科目),或者可能以不同的方式實作相同的特征。在OOP系統中,我們可以說教授和學生都從人那裡繼承。

總結

繼續閱讀