原型是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系統中,我們可以說教授和學生都從人那裡繼承。