Skip to content

对象原型 (Prototypes)

每个 JavaScript 对象都链接到另一个称为其“原型”(prototype) 的对象。这个原型对象也有自己的原型,以此类推,形成一个“原型链”(prototype chain)。

当你尝试访问对象的属性或方法时,JavaScript 会首先在该对象自身查找。如果找不到,它会在其原型上查找,然后是原型的原型,以此类推,直到找到属性或链条以 null 结束。

这种机制被称为“原型继承”(prototypal inheritance),它是 JavaScript 对象如何继承特性的基础。

使用对象字面量 ({}) 或 new Object() 创建的对象继承自 Object.prototype。

使用“构造函数”(constructor function) 创建的对象(例如,new Date())继承自构造函数的 prototype 属性(例如,Date.prototype)。

Object.prototype 位于大多数对象的原型链顶端。它提供了诸如 toString()、hasOwnProperty() 等常见方法。

创建共享同一原型的对象的常用方法是使用构造函数:

// 用于创建 Person 对象的构造函数
function Person(first, last, age, eyeColor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyeColor;
}
// 给 Person 原型添加一个方法
Person.prototype.getFullName = function() {
return `${this.firstName} ${this.lastName}`;
};

使用构造函数时,你需要使用 new 关键词来创建新的对象(即“实例”(instance))。这些实例将继承自 Person.prototype。

const myFather = new Person("John", "Doe", 50, "blue");
const myMother = new Person("Sally", "Rally", 48, "green");
console.log(myFather.getFullName()); // 输出:John Doe
console.log(myMother.getFullName()); // 输出:Sally Rally
添加到构造函数的 prototype 属性上的方法会由所有实例共享,这使其更加节省内存。

ES6 引入了 class 语法,它提供了一种更清晰、更熟悉的方式(对于来自基于类的语言的开发者而言)来创建对象和管理继承。本质上,JavaScript 的类仍然使用原型继承。

这是使用 class 语法的 Person 示例:

class Person {
constructor(first, last, age, eyeColor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyeColor;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
// 静态属性(属于类,不属于实例)
static species = "Homo sapiens";
}
const myFriend = new Person("Alex", "Smith", 30, "brown");
console.log(myFriend.getFullName()); // 输出:Alex Smith
console.log(Person.species); // 输出:Homo sapiens

你可以直接为单个对象实例添加属性或方法。这些属性或方法仅存在于该特定对象上。

myFather.nationality = "English"; // 只添加到 myFather 对象上的属性
console.log(myFather.nationality); // 输出:English
// console.log(myMother.nationality); // 输出:undefined
myMother.greet = function() { // 只添加到 myMother 对象上的方法
return `Hello, I am ${this.getFullName()}`;
};
console.log(myMother.greet()); // 输出:Hello, I am Sally Rally
// myFather.greet(); // 这将导致错误

你可以向现有构造函数的 prototype 属性添加新的属性和方法。这将使它们对该构造函数的所有现有和将来的实例都可用(除非某个实例具有同名属性)。

// 通过原型给所有 Person 实例添加新方法
Person.prototype.getAgeInYears = function() {
return `${this.age} years old`;
};
console.log(myFather.getAgeInYears()); // 输出:50 years old
console.log(myMother.getAgeInYears()); // 输出:48 years old
**重要提示:**仅修改你自己定义的原型。切勿修改标准内置 JavaScript 对象的原型,如 Object.prototype、Array.prototype 等。这被认为是糟糕的做法,因为它可能导致不可预测的行为,并与其他代码或未来的 JavaScript 版本冲突。