原型链:JavaScript 对象继承的底层脉络

🤍 前端开发工程师、技术日更博主、已过CET6
🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1
🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》
🍚 蓝桥云课签约作者、上架课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入门到实战全面掌握 uni-app》
文章目录
一、原型链的核心概念:从对象到蓝图的映射
在 JavaScript 中,原型(Prototype)是理解对象继承的核心机制。每个对象(除了 null)都关联着一个原型对象,该原型对象又可能指向另一个原型,形成一条层层嵌套的链条,即原型链(Prototype Chain)。原型链的本质是 JavaScript 实现**基于原型的继承(Prototype-based Inheritance)**的底层结构,它使得对象可以继承另一个对象的属性和方法。
二、原型链的构建基石:三个关键角色
(一)对象(Object)与 [[Prototype]] 内部属性
- 所有对象的隐式连接:每个对象在创建时,都会自动拥有一个内部属性
[[Prototype]](非标准环境中称为__proto__),该属性指向其原型对象。const obj = { key: 'value' }; console.log(obj.__proto__ === Object.prototype); // true- 浏览器环境中可通过
__proto__访问,但更推荐使用标准方法Object.getPrototypeOf()。
- 浏览器环境中可通过
(二)函数的 prototype 属性
- 构造函数的显式原型:函数对象(如
Function、Object、自定义构造函数)拥有一个prototype属性,该属性是一个对象,默认包含constructor属性(指向函数自身)。function Person(name) { this.name = name; } console.log(Person.prototype.constructor === Person); // true- 当使用
new Person()创建实例时,实例的[[Prototype]]会指向Person.prototype。
- 当使用
(三)原型对象的终极指向:Object.prototype
- 原型链的顶端:所有内置对象的原型链最终都会指向
Object.prototype,它是原型链的根节点。Object.prototype的[[Prototype]]为null,标志着原型链的终止。console.log(Object.prototype.__proto__); // null
三、原型链的工作原理:属性访问与继承
(一)属性查找机制
当访问对象的属性或方法时,JavaScript 会按照以下顺序在原型链中查找:
- 当前对象自身属性:首先检查对象本身是否拥有该属性(包括自有属性和可枚举属性)。
- 原型对象:若当前对象没有该属性,沿
[[Prototype]]链向上查找原型对象。 - 递归查找直至
null:若沿途所有原型对象都不存在该属性,则返回undefined。
示例:原型链属性查找
// 定义构造函数
function Animal() {
this.type = 'animal';
}
Animal.prototype.speak = function() {
return 'sound';
};
function Dog(name) {
Animal.call(this); // 继承自有属性
this.name = name;
}
// 建立原型链:Dog.prototype.__proto__ = Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数引用
// 创建实例
const husky = new Dog('Husky');
// 属性查找路径:
// 1. husky 自身:type(animal)、name(Husky)
// 2. Dog.prototype:constructor(Dog)
// 3. Animal.prototype:speak(函数)
// 4. Object.prototype:toString()、hasOwnProperty() 等
console.log(husky.speak()); // 输出 "sound"(从 Animal.prototype 继承)
(二)属性修改与原型链的关系
- 自有属性优先:若对象自身已存在某属性,会直接覆盖原型链中的同名属性。
husky.speak = function() { return 'woof!'; }; console.log(husky.speak()); // 输出 "woof!"(使用自有方法) - 原型对象的修改会影响所有实例:若修改原型对象的属性,所有继承该原型的实例都会受到影响。
Animal.prototype.speak = function() { return 'new sound'; }; console.log(husky.speak()); // 输出 "new sound"(原型链已更新)
四、原型链的应用场景:实现继承与扩展
(一)简单继承模式
通过原型链实现类的继承,是 JavaScript 中最经典的继承方式。
步骤解析:
- 定义父类:封装通用属性和方法(如
Animal的type和speak)。 - 定义子类:通过
Object.create()建立子类原型与父类原型的连接。 - 构造函数绑定:在子类构造函数中调用
父类.call(this),继承父类的自有属性。
ES5 继承示例
function Shape(color) {
this.color = color;
}
Shape.prototype.getArea = function() { /* 通用逻辑 */ };
function Circle(radius, color) {
Shape.call(this, color); // 继承自有属性
this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype); // 继承原型方法
Circle.prototype.constructor = Circle;
const redCircle = new Circle(5, 'red');
console.log(redCircle.color); // "red"(继承自 Shape)
(二)扩展内置对象
利用原型链为内置对象(如 Array、String)添加自定义方法,但需谨慎避免污染原生对象。
// 为数组添加去重方法
Array.prototype.unique = function() {
return [...new Set(this)];
};
const arr = [1, 2, 2, 3];
console.log(arr.unique()); // [1, 2, 3]
五、原型链的局限性与替代方案
(一)原型链的常见问题
- 引用类型属性共享:原型对象的引用类型属性会被所有实例共享,可能导致意外修改。
function Car() {} Car.prototype.parts = ['engine', 'wheel']; const car1 = new Car(); const car2 = new Car(); car1.parts.push('seat'); console.log(car2.parts); // 输出 ["engine", "wheel", "seat"](共享问题) - 子类构造函数无法直接访问父类原型方法:需通过
super关键字(ES6 语法)或显式调用父类方法。
(二)ES6 类语法:语法糖下的原型链
ES6 的 class 和 extends 关键字本质上仍是基于原型链实现继承,只是语法更简洁且避免了部分坑点:
class Animal {
constructor(type) {
this.type = type;
}
speak() {
return 'sound';
}
}
class Dog extends Animal {
constructor(name, type) {
super(type); // 必须先调用 super()
this.name = name;
}
}
const husky = new Dog('Husky', 'dog');
console.log(husky.speak()); // "sound"(原型链继承)
super关键字用于访问父类原型方法,内部处理了原型链的绑定。
(三)替代继承模式
- 对象关联继承(Object Composition):通过组合对象而非继承实现代码复用,避免原型链的复杂性。
const animal = { type: 'animal', speak() { return 'sound' } }; const dog = { ...animal, name: 'Husky', speak() { return 'woof!' } }; - ES6
Object.setPrototypeOf():显式设置对象的原型,但性能低于构造函数继承。
六、原型链的性能与最佳实践
- 避免深层原型链:过长的原型链会导致属性查找变慢,建议继承层级不超过3层。
- 优先使用对象组合:对于复杂逻辑,组合模式比继承更灵活且性能更优。
- 谨慎扩展内置原型:避免覆盖原生方法(如
Array.prototype.push),防止全局作用域污染。 - 使用
hasOwnProperty判断自有属性:在遍历对象属性时,通过obj.hasOwnProperty(key)避免访问原型链属性。for (const key in obj) { if (obj.hasOwnProperty(key)) { // 处理自有属性 } }
七、总结:原型链的本质与延伸
原型链是 JavaScript 区别于传统类式语言(如 Java、C#)的核心特性,其本质是通过对象间的隐式连接实现动态继承。理解原型链不仅能掌握 this、call/apply/bind 等关键机制,还能深入理解 JavaScript 的“弱类型”“动态性”设计哲学。尽管 ES6 引入了类语法,但原型链仍是 JavaScript 引擎实现继承的底层逻辑。在实际开发中,需根据场景选择合适的继承模式,平衡代码简洁性与性能,避免滥用原型链导致的维护难题。
更多推荐


所有评论(0)