原型与原型链:JavaScript 对象继承的底层机制
想象你在使用一个工具箱时,如果发现自己缺少某个工具,你会先看看箱子里有没有,如果没有,就会向邻居或者工具房借用。JavaScript 的对象系统也是这样工作的——每个对象都有自己的工具箱,当需要某个属性或方法时,先在自己的工具箱里找,找不到就沿着"邻里链"向上查找,直到找到或者到达链的末端。
这个巧妙的机制就是 JavaScript 的核心特性:原型与原型链。
什么是原型:对象的家族谱系
在 JavaScript 中,每个对象都有一个隐藏的内部属性 [[Prototype]](在浏览器环境中,我们可以通过 __proto__ 访问它),这个属性指向另一个对象。这个被指向的对象就是当前对象的原型。
// 创建一个简单的对象
const person = {
name: "Sarah",
age: 29,
introduce() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old`);
},
};
// 查看对象的原型指向
console.log(person.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true - 原型链的终点即使是这个简单的 person 对象,也已经通过原型链继承了许多内置方法:
// person 对象自身没有 toString 方法
console.log("toString" in person); // true
console.log(person.hasOwnProperty("toString")); // false
// 但我们可以正常调用它,因为它在原型链上
console.log(person.toString()); // "[object Object]"
// hasOwnProperty 方法也是继承来的
console.log(person.hasOwnProperty("name")); // true - person 有自己的 name 属性
console.log(person.hasOwnProperty("toString")); // false - toString 在原型上prototype 与 proto:函数与对象的双胞胎
理解 JavaScript 的原型系统,最关键的就是要区分 prototype 和 __proto__ 这两个概念。它们虽然名字相似,但作用完全不同。
prototype 属性:函数的设计蓝图
prototype 是构造函数特有的属性,它定义了通过该函数创建的对象的原型。
function Vehicle(brand) {
this.brand = brand;
this.speed = 0;
}
// Vehicle.prototype 就是所有 Vehicle 实例的原型
console.log(Vehicle.prototype); // { constructor: f Vehicle() }
// 在原型上添加方法,所有实例都会继承
Vehicle.prototype.start = function () {
console.log(`${this.brand} vehicle is starting...`);
this.speed = 10;
};
Vehicle.prototype.accelerate = function (amount) {
this.speed += amount;
console.log(`${this.brand} is accelerating to ${this.speed} km/h`);
};
// 创建实例
const car = new Vehicle("Toyota");
// car 的原型指向 Vehicle.prototype
console.log(car.__proto__ === Vehicle.prototype); // trueproto 属性:对象的家谱链
__proto__ 是对象特有的属性,它指向该对象的原型。
function Vehicle(brand) {
this.brand = brand;
this.speed = 0;
}
const car = new Vehicle("Toyota");
// car.__proto__ 指向 Vehicle.prototype
console.log(car.__proto__ === Vehicle.prototype); // true
// Vehicle.prototype.__proto__ 指向 Object.prototype
console.log(Vehicle.prototype.__proto__ === Object.prototype); // true
// Object.prototype.__proto__ 是 null,原型链的终点
console.log(Object.prototype.__proto__ === null); // true记忆技巧:
prototype属于函数,是函数的"设计图"__proto__属于对象,是对象的"出身证明"
原型链的查找机制:寻找宝藏的旅程
当你访问一个对象的属性时,JavaScript 引擎会执行一个精确的查找过程:
- 首先检查对象自身:查看对象是否有该属性
- 沿着原型链向上查找:如果没有,查看
__proto__指向的原型对象 - 继续向上追溯:重复步骤 2,直到找到属性或到达
null
function LivingBeing() {
this.isAlive = true;
this.bornTime = Date.now();
}
LivingBeing.prototype.breathe = function () {
console.log(`${this.constructor.name} is breathing...`);
};
LivingBeing.prototype.getAge = function () {
const age = Math.floor((Date.now() - this.bornTime) / 1000);
return `${age} seconds old`;
};
function Animal(type, species) {
LivingBeing.call(this);
this.type = type;
this.species = species;
}
// 建立原型链:Animal.prototype -> LivingBeing.prototype
Animal.prototype = Object.create(LivingBeing.prototype);
Animal.prototype.constructor = Animal;
Animal.prototype.speak = function () {
console.log(`${this.species} makes a sound`);
};
Animal.prototype.move = function (speed) {
console.log(`${this.species} moves at ${speed} km/h`);
};
function Dog(name, breed) {
Animal.call(this, "mammal", "dog");
this.name = name;
this.breed = breed;
}
// 建立原型链:Dog.prototype -> Animal.prototype -> LivingBeing.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {
console.log(`${this.name} barks: Woof! Woof!`);
};
Dog.prototype.wagTail = function () {
console.log(`${this.name} wags tail happily`);
};
const myDog = new Dog("Max", "Golden Retriever");
// 让我们跟踪属性查找过程:
console.log(myDog.name); // "Max" - 在 myDog 对象上找到
// bark 方法的查找路径:
// 1. myDog.bark - 在 myDog 对象上查找 → 未找到
// 2. myDog.__proto__ (Dog.prototype).bark - 找到!
myDog.bark(); // "Max barks: Woof! Woof!"
// speak 方法的查找路径:
// 1. myDog.speak - 在 myDog 对象上查找 → 未找到
// 2. Dog.prototype.speak - 在 Dog.prototype 上查找 → 未找到
// 3. Animal.prototype.speak - 在 Animal.prototype 上查找 → 找到!
myDog.speak(); // "dog makes a sound"
// breathe 方法的查找路径:
// 1. myDog.breathe - 未找到
// 2. Dog.prototype.breathe - 未找到
// 3. Animal.prototype.breathe - 未找到
// 4. LivingBeing.prototype.breathe - 找到!
myDog.breathe(); // "Dog is breathing..."
// getAge 方法的查找路径:
// 1. myDog.getAge - 未找到
// 2. Dog.prototype.getAge - 未找到
// 3. Animal.prototype.getAge - 未找到
// 4. LivingBeing.prototype.getAge - 找到!
console.log(myDog.getAge()); // "X seconds old"
// toString 方法的查找路径:
// 一直向上到 Object.prototype 才找到
console.log(myDog.toString()); // "[object Object]"这个查找机制就像你在图书馆找一本书的过程:先在自己的书架上找,没有就去班级图书角,没有就去学校图书馆,最终可能在市图书馆找到。
原型链的实际应用场景
1. 方法共享:节省内存的智慧
原型链最常见的用途是让多个对象共享方法,这样可以节省大量内存:
function Smartphone(brand, model, storage) {
this.brand = brand;
this.model = model;
this.storage = storage;
this.apps = [];
}
// 将方法放在原型上,所有实例共享
Smartphone.prototype.powerOn = function () {
console.log(`${this.brand} ${this.model} is booting up...`);
};
Smartphone.prototype.installApp = function (appName, size) {
if (this.storage >= size) {
this.apps.push({ name: appName, size: size });
this.storage -= size;
console.log(`${appName} installed successfully`);
} else {
console.log(`Not enough storage. Need ${size}MB, have ${this.storage}MB`);
}
};
Smartphone.prototype.listApps = function () {
console.log(`Installed apps on ${this.brand} ${this.model}:`);
this.apps.forEach((app) => console.log(`- ${app.name} (${app.size}MB)`));
};
Smartphone.prototype.uninstallApp = function (appName) {
const appIndex = this.apps.findIndex((app) => app.name === appName);
if (appIndex !== -1) {
const app = this.apps[appIndex];
this.apps.splice(appIndex, 1);
this.storage += app.size;
console.log(`${appName} uninstalled successfully`);
} else {
console.log(`${appName} not found`);
}
};
// 创建多个实例
const iPhone = new Smartphone("Apple", "iPhone 15", 128);
const galaxy = new Smartphone("Samsung", "Galaxy S24", 256);
// 所有实例共享相同的方法
iPhone.powerOn(); // "Apple iPhone 15 is booting up..."
galaxy.powerOn(); // "Samsung Galaxy S24 is booting up..."
// 使用共享的方法
iPhone.installApp("WhatsApp", 150);
iPhone.installApp("Instagram", 200);
galaxy.installApp("TikTok", 300);
iPhone.listApps();
galaxy.listApps();
// 验证方法确实是共享的
console.log(iPhone.powerOn === galaxy.powerOn); // true
console.log(iPhone.installApp === galaxy.installApp); // true
// 内存效率:如果有10000个实例,只需要一个方法副本
// 而不是每个实例都有自己的方法副本2. 属性覆盖:个性与共性的平衡
子对象可以覆盖原型上的属性,同时还能通过原型链访问父级的属性:
function Employee(name, position) {
this.name = name;
this.position = position;
this.workingHours = 40;
}
Employee.prototype.department = "General";
Employee.prototype.salary = 50000;
Employee.prototype.getJobInfo = function () {
return `${this.position} in ${this.department} department`;
};
Employee.prototype.requestLeave = function (days) {
console.log(`${this.name} is requesting ${days} days leave`);
};
function Manager(name, position, teamSize) {
Employee.call(this, name, position);
this.teamSize = teamSize;
}
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
// 覆盖原型上的属性
Manager.prototype.department = "Management";
Manager.prototype.salary = 80000;
Manager.prototype.workingHours = 45;
// 添加管理器特有方法
Manager.prototype.approveLeave = function (employee, days) {
console.log(
`Manager ${this.name} approved ${employee.name}'s ${days} days leave`
);
};
Manager.prototype.runTeamMeeting = function () {
console.log(
`${this.name} is conducting a team meeting for ${this.teamSize} team members`
);
};
const employee1 = new Employee("John Doe", "Developer");
const manager1 = new Manager("Jane Smith", "Senior Manager", 8);
console.log(employee1.getJobInfo()); // "Developer in General department"
console.log(manager1.getJobInfo()); // "Senior Manager in Management department"
console.log(employee1.salary); // 50000 (来自原型)
console.log(manager1.salary); // 80000 (覆盖了原型的值)
// 仍然可以使用原型上的方法
employee1.requestLeave(5); // "John Doe is requesting 5 days leave"
manager1.requestLeave(3); // "Jane Smith is requesting 3 days leave"
// 管理器特有的方法
manager1.approveLeave(employee1, 5); // "Manager Jane Smith approved John Doe's 5 days leave"
manager1.runTeamMeeting(); // "Jane Smith is conducting a team meeting for 8 team members"3. 检查属性来源:辨别自有与继承
使用 hasOwnProperty 方法可以区分属性是对象自身的还是继承的:
function Task(title, priority) {
this.title = title;
this.priority = priority;
this.completed = false;
}
Task.prototype.category = "General";
Task.prototype.description = "Default task description";
Task.prototype.toggleComplete = function () {
this.completed = !this.completed;
console.log(
`Task "${this.title}" marked as ${this.completed ? "completed" : "pending"}`
);
};
Task.prototype.getDetails = function () {
return `${this.title} (${this.priority} priority) - ${
this.completed ? "Done" : "Pending"
}`;
};
const task1 = new Task("Write report", "High");
// 检查属性来源
console.log(task1.hasOwnProperty("title")); // true (自有属性)
console.log(task1.hasOwnProperty("priority")); // true (自有属性)
console.log(task1.hasOwnProperty("completed")); // true (自有属性)
console.log(task1.hasOwnProperty("category")); // false (继承属性)
console.log(task1.hasOwnProperty("description")); // false (继承属性)
console.log(task1.hasOwnProperty("toggleComplete")); // false (继承方法)
// in 操作符会查找整个原型链
console.log("title" in task1); // true
console.log("category" in task1); // true
console.log("toggleComplete" in task1); // true
// 自定义函数判断属性类型
function getPropertyInfo(obj, prop) {
const hasOwn = obj.hasOwnProperty(prop);
const inProto = prop in obj;
if (hasOwn) {
return `${prop}: 自有属性`;
} else if (inProto) {
return `${prop}: 继承属性`;
} else {
return `${prop}: 不存在`;
}
}
console.log(getPropertyInfo(task1, "title")); // "title: 自有属性"
console.log(getPropertyInfo(task1, "category")); // "category: 继承属性"
console.log(getPropertyInfo(task1, "nonexistent")); // "nonexistent: 不存在"原型链的高级操作
1. 动态修改原型:实时添加能力
function Calculator() {
this.result = 0;
}
Calculator.prototype.add = function (number) {
this.result += number;
return this;
};
Calculator.prototype.subtract = function (number) {
this.result -= number;
return this;
};
Calculator.prototype.getResult = function () {
return this.result;
};
const calc = new Calculator();
calc.add(10).subtract(3).add(5);
console.log(calc.getResult()); // 12
// 动态添加新方法到原型
Calculator.prototype.multiply = function (number) {
this.result *= number;
return this;
};
Calculator.prototype.divide = function (number) {
if (number !== 0) {
this.result /= number;
} else {
console.log("Cannot divide by zero");
}
return this;
};
calc.multiply(2).divide(4);
console.log(calc.getResult()); // 4.5
// 现有的计算器实例也能使用新方法
const calc2 = new Calculator();
calc2.add(20).multiply(3);
console.log(calc2.getResult()); // 602. 原型链检测:理解对象关系
// 获取对象的原型链
function getPrototypeChain(obj) {
const chain = [];
let current = obj;
while (current !== null) {
chain.push(current.constructor.name || "Object");
current = Object.getPrototypeOf(current);
}
return chain.join(" -> ");
}
function Shape(name) {
this.name = name;
}
function Circle(radius) {
Shape.call(this, "Circle");
this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
function ColoredCircle(radius, color) {
Circle.call(this, radius);
this.color = color;
}
ColoredCircle.prototype = Object.create(Circle.prototype);
ColoredCircle.prototype.constructor = ColoredCircle;
const coloredCircle = new ColoredCircle(5, "red");
console.log(getPrototypeChain(coloredCircle)); // "ColoredCircle -> Circle -> Shape -> Object"
// 检查对象是否是某个构造函数的实例
console.log(coloredCircle instanceof ColoredCircle); // true
console.log(coloredCircle instanceof Circle); // true
console.log(coloredCircle instanceof Shape); // true
console.log(coloredCircle instanceof Array); // false
// 更精确的原型检查
function isPrototypeOf(Constructor, obj) {
return obj.constructor === Constructor;
}
console.log(isPrototypeOf(ColoredCircle, coloredCircle)); // true
console.log(isPrototypeOf(Circle, coloredCircle)); // false原型链的性能考虑
1. 避免过深的原型链
过深的原型链会影响属性查找的性能,应该保持合理的深度:
// ❌ 不推荐:过深的原型链
function Level1() {}
function Level2() {}
function Level3() {}
function Level4() {}
function Level5() {}
function Level6() {}
Level2.prototype = Object.create(Level1.prototype);
Level3.prototype = Object.create(Level2.prototype);
Level4.prototype = Object.create(Level3.prototype);
Level5.prototype = Object.create(Level4.prototype);
Level6.prototype = Object.create(Level5.prototype);
const deepObj = new Level6();
// 查找深层属性需要遍历整个原型链,性能较差
// ✅ 推荐:合理的原型链深度(通常不超过3-4层)
function BaseClass() {}
function MiddleClass() {}
function TopClass() {}
MiddleClass.prototype = Object.create(BaseClass.prototype);
TopClass.prototype = Object.create(MiddleClass.prototype);
const reasonableObj = new TopClass();
// 查找属性只需要遍历较少的原型链2. 原型链污染风险
// ❌ 危险:修改 Object.prototype
// 这会影响到所有对象,可能导致意外的行为
Object.prototype.someDangerousMethod = function () {
console.log("This method affects all objects!");
};
const obj = { a: 1 };
obj.someDangerousMethod(); // 现在所有对象都有了这个方法
// ✅ 安全:创建自己的工具对象
const StringUtils = {
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
reverse(str) {
return str.split("").reverse().join("");
},
truncate(str, length) {
return str.length > length ? str.slice(0, length) + "..." : str;
},
};
// 使用工具对象,而不是污染全局原型
const text = "hello world";
console.log(StringUtils.capitalize(text)); // "Hello world"3. 修改原型的时机
function Person(name) {
this.name = name;
}
const john = new Person("John");
// 在创建实例后修改原型
Person.prototype.sayHello = function () {
console.log(`Hello, I'm ${this.name}`);
};
// john 对象可以访问到新添加的方法
// 因为 JavaScript 会动态查找原型链
john.sayHello(); // "Hello, I'm John"
// 但更推荐的做法是在创建实例前完成所有原型设置现代 JavaScript 中的替代方案
虽然原型链是 JavaScript 的核心机制,但在现代 JavaScript 开发中,我们通常使用更高级的语法。
1. Class 语法:原型的现代包装
// 现代的 Class 语法(底层仍然是原型链)
class Animal {
constructor(type) {
this.type = type;
}
speak() {
console.log(`${this.type} makes a sound`);
}
move(speed) {
console.log(`${this.type} moves at ${speed} km/h`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super("dog"); // 调用父类构造函数
this.name = name;
this.breed = breed;
}
bark() {
console.log(`${this.name} (${this.breed}) barks: Woof!`);
}
// 重写父类方法
speak() {
console.log(`${this.name} barks loudly`);
}
// 调用父类方法
getDetails() {
return `${this.name} is a ${this.breed} dog`;
}
}
const myDog = new Dog("Max", "Golden Retriever");
myDog.speak(); // 重写的方法
myDog.move(15); // 继承的方法
myDog.bark(); // 自有的方法
console.log(myDog.getDetails()); // "Max is a Golden Retriever dog"2. Object.create():直接创建原型对象
// 创建具有指定原型的对象
const animalProto = {
type: "unknown",
speak() {
console.log(`${this.type} makes a sound`);
},
move(speed) {
console.log(`${this.type} moves at ${speed} km/h`);
},
};
const cat = Object.create(animalProto);
cat.type = "cat";
cat.meow = function () {
console.log("Cat meows: Meow!");
};
cat.speak(); // 继承的方法
cat.meow(); // 自有的方法
// 创建更复杂的原型链
const mammalProto = Object.create(animalProto, {
type: { value: "mammal" },
warmBlooded: { value: true },
});
mammalProto.giveBirth = function () {
console.log(`${this.type} gives birth to live young`);
};
const dog = Object.create(mammalProto);
dog.type = "dog";
dog.bark = function () {
console.log("Dog barks: Woof!");
};
dog.speak(); // "dog makes a sound" (来自 animalProto)
dog.giveBirth(); // "dog gives birth to live young" (来自 mammalProto)
dog.bark(); // "Dog barks: Woof!" (自有方法)3. Object.setPrototypeOf():动态设置原型
const baseObject = {
commonMethod() {
console.log("This is a common method");
},
};
const extendedObject = {
specificMethod() {
console.log("This is a specific method");
},
};
// 动态设置原型
Object.setPrototypeOf(extendedObject, baseObject);
extendedObject.commonMethod(); // 可以访问基对象的方法
extendedObject.specificMethod(); // 可以访问自己的方法
// 验证原型关系
console.log(extendedObject.__proto__ === baseObject); // true总结:掌握原型的力量
原型与原型链是 JavaScript 实现对象间属性和方法共享的基础机制。理解它们的工作原理对于深入掌握 JavaScript 至关重要:
核心概念回顾
- 每个对象都有一个原型:通过
__proto__属性访问 - 每个函数都有一个 prototype 属性:决定了其创建实例的原型
- 属性查找沿着原型链进行:直到找到或到达链尾(null)
- 方法应该放在原型上:以实现内存共享和代码复用
- 合理使用原型链:可以构建高效的继承体系
实际应用建议
- 方法共享:将通用方法放在原型上,让所有实例共享
- 属性覆盖:子对象可以覆盖原型属性,同时保留访问父属性的能力
- 性能优化:避免过深的原型链,保持合理的继承层次
- 安全编程:避免污染全局原型,创建专门的工具对象
虽然现代 JavaScript 提供了 Class 等更友好的语法,但底层仍然是基于原型链机制。理解原型不仅能帮助你更好地使用 JavaScript,还能在遇到复杂问题时找到解决方案,编写出更加高效和优雅的代码。
原型链是 JavaScript 的精髓,掌握它就意味着真正理解了这门语言的本质。在 JavaScript 的世界里,没有孤立的对象,每个对象都活在原型链这个大家族中。