介绍
原型是 JavaScript 对象之间相互继承属性的机制。本文将解释什么是原型、原型链的工作原理以及如何为对象设置原型。.
先决条件
了解 JavaScript 函数,熟悉 JavaScript 基础知识(参见“入门”和“构建模块”),以及面向对象 JavaScript 原则(参见“对象简介”)。.
原型链
在浏览器控制台中,尝试创建一个字面对象:
const myObject = {
city: "Madrid",
greet() {
console.log(`Greetings from ${this.city}`);
},
};
myObject.greet(); // Greetings from Madrid这是一个包含一个数据属性 city 和一个方法 greet() 的对象。如果您在控制台中输入对象名称,后跟一个句点,例如 myObject.,控制台将显示此对象所有可用属性的列表。您会发现,除了 city 和 greeting 之外,还有许多其他属性!
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf尝试访问其中一个:
myObject.toString(); // "[object Object]"
它能正常工作(即使不清楚 toString() 的具体作用)。.
这些附加属性是什么?它们从何而来?
JavaScript 中的每个对象都有一个名为 prototype 的内置属性。prototype 本身也是一个对象,因此一个原型可以拥有自己的原型,从而形成所谓的原型链。当遇到一个原型的原型为 null 时,这条链就会终止。.
注意:指向对象原型的属性并不直接称为原型。虽然名称并非标准用法,但实际上所有浏览器都使用 `__proto__`。访问对象原型的标准方法是使用 `Object.getPrototypeOf()` 方法。.
当您尝试访问对象的属性时:如果该属性在对象本身中找不到,则会搜索该属性的原型。如果仍然找不到,则会搜索实例原型,依此类推,直到找到该属性或到达查找链的末尾,此时将返回 undefined。.
所以当我们调用 myObject.toString() 时,浏览器:
- 在 myObject 中查找 toString
- 我在那里找不到,所以它在 myObject 对象原型中查找 toString 方法。
- 他在那里找到了它,并给它打了个电话。.
myObject 的原型是什么?要找出原型,我们可以使用 Object.getPrototypeOf() 函数:
Object.getPrototypeOf(myObject); // Object { }
这是一个名为 Object.prototype 的对象,它是所有对象默认拥有的最基本原型。Object.prototype 的原型为空,因此它位于原型链的末端:
对象的原型并不总是 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.getTime() 时,你调用的是在 Date.prototype 中定义的方法。.
阴影属性
如果在一个对象上定义了一个属性,而该对象的原型中已经定义了一个同名属性,会发生什么情况?让我看看:
const myDate = new Date(1995, 11, 17);
console.log(myDate.getTime()); // 819129600000
myDate.getTime = function () {
console.log("something else!");
};
myDate.getTime(); // 'something else!'根据原型链的描述,这应该是可以预见的。当我们调用 `getTime()` 时,浏览器首先会在 `myDate` 中查找同名属性,只有当 `myDate` 中没有定义该属性时才会检查原型。因此,当我们在 `myDate` 中添加 `getTime()` 时,会调用 `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__ 的属性中)。.
因此,如果我们设置构造函数的原型,就可以确保所有使用该构造函数创建的对象都具有该原型:
const personPrototype = {
greet() {
console.log(`hello, my name is ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// or
// Person.prototype.greet = personPrototype.greet;我们在此创造:
- 具有 greet() 方法的 personPrototype 对象
- Person() 构造函数,用于初始化要创建的人员的名称。.
然后我们使用 Object.assign 将 personPrototype 中定义的方法放入 Person 函数的 prototype 属性中。.
执行此代码后,使用 Person.prototype() 创建的对象将收到其原型,该原型会自动包含 greet 方法。.
const reuben = new Person("Reuben");
reuben.greet(); // hello, my name is Reuben!这也解释了为什么我们之前说 myDate 的原型叫做 Date.prototype:这是 Date 构造函数的原型属性。.
自有房产
我们使用上面 Person 构造函数创建的对象具有两个属性:
- 名称属性,在构造函数中设置,因此它会直接显示在 Person 对象上。
- 原型中设置了 greet() 方法。.
这种模式很常见:方法定义在原型中,而数据属性定义在构造函数中。原因是,我们创建的每个对象的方法通常都相同,而我们往往希望每个对象的数据属性都有其唯一的值(就像这里每个人都有不同的名字一样)。.
直接在对象上定义的属性(例如这里的 name)称为对象自身属性,您可以使用静态的 Object.hasOwn() 方法检查某个属性是否为特定属性:
const irma = new Person("Irma");
console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false注意:您也可以在此处使用非静态的 Object.hasOwnProperty() 方法,但我们建议尽可能使用 Object.hasOwn()。.
原型与继承
原型是 JavaScript 中一项强大而灵活的功能,它允许代码重用和对象组合。.
它们特别支持某种形式的继承。继承是面向对象编程语言的一个特性,它允许程序员表达系统中某些对象是其他对象的更专门化的版本这一概念。.
例如,如果我们对一所学校进行建模,我们可能会有教师和学生:他们都是人,因此他们有一些共同的属性(例如,他们都有姓名),但各自可能添加额外的属性(例如,教师有他们教授的科目),或者他们可能以不同的方式实现相同的属性。在面向对象编程系统中,我们可以说教师和学生都继承自 People 类。.
你可以看到,在 JavaScript 中,如果 Professor 和 Student 对象可以有 Person 原型,它们就可以继承共同的属性,同时添加和重新定义需要不同的属性。.
下一篇文章我们将讨论继承以及面向对象编程语言的其他核心特性,并了解 JavaScript 如何支持它们。.
结果
本文涵盖了 JavaScript 对象原型,包括对象原型链如何允许对象彼此继承属性、原型属性以及如何使用它向构造函数添加方法,以及其他相关主题。.









