在 JavaScript 的世界里,原型链是一个贯穿始终的核心概念,它不仅支撑着 JavaScript 的继承机制,也决定了对象属性的查找规则。对于前端开发者而言,搞懂原型链,就相当于掌握了 JavaScript 面向对象编程的 “密码”。今天,我们就从关键要点出发,一步步揭开原型链的神秘面纱。

一、先搞懂两个 “原型”:prototype__proto__

要理解原型链,首先得区分两个容易混淆的概念 ——prototype__proto__,这是原型链的 “基石”。

1. prototype:函数的 “专属属性”

在 JavaScript 中,所有函数(除箭头函数外)都自带一个prototype属性,它是一个对象,我们称之为 “原型对象”。这个原型对象有一个默认的constructor属性,指向该函数本身。

举个例子:

// 定义一个构造函数
function Person(name) {
this.name = name;
}
// 访问函数的prototype属性
console.log(Person.prototype);
// 输出:{constructor: ƒ Person(name), __proto__: Object}
console.log(Person.prototype.constructor === Person);
// 输出:true(constructor指向原函数)

我们可以在prototype上添加属性或方法,这些内容会被该函数创建的所有实例 “共享”—— 这也是原型链实现代码复用的核心逻辑。

2. __proto__:对象的 “隐藏链接”

所有对象(除nullundefined外)都有一个__proto__属性(ES6 后可通过Object.getPrototypeOf()安全访问),它指向创建该对象的 “构造函数的prototype”。简单来说,__proto__是对象与它的原型对象之间的 “桥梁”。

还是用上面的Person构造函数举例:

// 创建Person的实例
const zhangsan = new Person("张三");
// 实例的__proto__指向构造函数的prototype
console.log(zhangsan.__proto__ === Person.prototype);
// 输出:true

这里要注意:__proto__是对象的属性,prototype是函数的属性,二者的指向关系是 “对象.proto → 构造函数.prototype”,这是原型链形成的基础。

二、原型链的本质:“层层向上” 的查找链条

理解了prototype__proto__的关系后,原型链就很好理解了 ——原型链是由__proto__串联起来的、从对象指向其原型对象,再指向原型对象的原型对象,直到Object.prototype的链条

1. 原型链的 “顶端”:Object.prototype

在 JavaScript 中,所有对象的原型链最终都会指向Object.prototype,而Object.prototype__proto__指向null(表示链条的终点)。我们用一张简单的关系图来展示:
在这里插入图片描述

用代码验证一下:

console.log(zhangsan.__proto__.__proto__ === Object.prototype);
// 输出:true(Person.prototype的原型是Object.prototype)
console.log(Object.prototype.__proto__);
// 输出:null(原型链顶端)

2. 原型链的核心作用:属性查找机制

当我们访问一个对象的属性时,JavaScript 会遵循以下规则:

  1. 首先在对象自身上查找该属性,如果找到则直接返回;

  2. 如果没找到,就通过__proto__它的原型对象上查找;

  3. 如果原型对象上也没有,就继续通过原型对象的__proto__向上查找,直到Object.prototype

  4. 如果Object.prototype上仍未找到,就返回undefined

这就是原型链的 “查找机制”,也是 “继承” 的本质。比如我们常用的toString()方法,其实就是Object.prototype上的方法,所有对象都能通过原型链访问到它:

console.log(zhangsan.toString());
// 输出:[object Object](zhangsan自身没有toString,从Object.prototype继承)

三、原型链与继承:JavaScript 的 “继承实现方式”

JavaScript 没有传统面向对象语言中的 “类”(ES6 的class本质也是原型链的语法糖),它的继承完全依赖原型链实现。这里我们通过一个实例,看看原型链如何实现 “子类继承父类”。

示例:用原型链实现继承

假设我们有一个Animal构造函数(父类),再定义一个Dog构造函数(子类),让Dog继承Animal的属性和方法:

// 父类:Animal
function Animal(type) {
this.type = type; // 动物类型(如“哺乳动物”)
}
// 父类原型上的方法
Animal.prototype.eat = function() {
console.log(`${this.type}需要吃东西`);
};
// 子类:Dog
function Dog(name) {
this.name = name; // 狗的名字
}
// 关键步骤:让Dog的原型指向Animal的实例
Dog.prototype = new Animal("哺乳动物");
// 修复constructor指向(否则Dog.prototype.constructor会指向Animal)
Dog.prototype.constructor = Dog;
// 子类原型上的方法
Dog.prototype.bark = function() {
console.log(`${this.name}在汪汪叫`);
};
// 创建Dog实例
const erha = new Dog("二哈");
// 测试继承的属性和方法
console.log(erha.type); // 输出:哺乳动物(从Animal继承)
erha.eat(); // 输出:哺乳动物需要吃东西(从Animal.prototype继承)
erha.bark(); // 输出:二哈在汪汪叫(自身原型上的方法)

这里的原型链关系是:

erha(Dog实例)
__proto__ → Dog.prototype(指向Animal实例)
__proto__ → Animal.prototype
__proto__ → Object.prototype
__proto__ → null

通过这种方式,Dog实例不仅能访问自身和Dog.prototype的属性,还能通过原型链访问AnimalObject的原型属性,实现了 “继承”。

四、原型链的常见 “坑” 与注意事项

理解原型链时,很容易因为概念混淆踩坑,这里总结几个关键注意事项:

1. 不要直接修改__proto__

__proto__是对象的 “隐藏属性”(ES6 标准中仅作为访问器存在),直接修改它会破坏原型链的稳定性,还会影响性能(因为浏览器会优化原型链查找,修改后优化失效)。如果需要修改原型,应通过修改构造函数的prototype实现。

2. null没有原型链

null是原型链的终点,它没有__proto__属性。如果尝试访问null.__proto__,会直接报错:

console.log(null.__proto__); // 报错:Cannot read property '__proto__' of null

3. 箭头函数没有prototype

箭头函数是 “简化版函数”,它没有prototype属性,也不能作为构造函数使用(用new调用箭头函数会报错)。因此,箭头函数无法参与原型链的构建。

4. 原型对象的修改会 “影响所有实例”

因为所有实例共享构造函数的prototype,如果修改了prototype,已创建的实例也会受到影响。比如:

// 先创建实例
const lisi = new Person("李四");
// 修改Person.prototype
Person.prototype.age = 18;
// 实例能访问到新添加的属性
console.log(lisi.age); // 输出:18

五、总结:原型链的核心要点

最后,我们用一句话总结原型链的核心:原型链是由__proto__串联的对象原型链条,它决定了属性查找规则,是 JavaScript 继承的基础,最终指向Object.prototype,终点为null

掌握原型链,不仅能帮我们理解class继承等高级概念的本质,还能在遇到 “属性找不到”“继承失效” 等问题时,快速定位根源。希望这篇文章能让你对原型链有更清晰的认识,下次再面对原型链相关问题时,就能从容应对啦!

posted on 2025-09-15 16:20  ycfenxi  阅读(8)  评论(0)    收藏  举报