原型是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系统中,我们可以说教授和学生都从人那里继承。
你可以看到在JavaScript中,如果和对象可以有原型,那么它们可以继承公共属性,同时添加和重新定义那些需要不同的属性。Professor
Student
Person
在下一篇文章中,我们将讨论继承以及面向对象编程语言的其他主要功能,并了解JavaScript如何支持它们。
总结
本文介绍了 JavaScript 对象原型,包括原型对象链如何允许对象彼此继承功能、prototype 属性以及如何使用它向构造函数添加方法,以及其他相关主题。