在 JavaScript 中,原型继承 是核心特性之一,它决定了对象如何共享属性和方法。然而,许多开发者对 prototype
、__proto__
和 constructor
的关系感到困惑。本文将通过代码示例和图解,彻底理清构造函数、实例和原型之间的"三角关系"。
1. 构造函数与原型对象
1.1 什么是构造函数?
在 JavaScript 中,构造函数 是一个普通函数,但通过 new
关键字调用时,它会创建一个新对象并初始化其属性。例如:
javascript
function User(name) {
this.name = name;
}
1.2 构造函数的 prototype
属性
每个函数(包括构造函数)都有一个 prototype
属性,它指向一个原型对象 。这个对象默认包含一个 constructor
属性,指向构造函数本身:
javascript
console.log(User.prototype);
// 输出:
// {
// constructor: ƒ User(name),
// __proto__: Object.prototype
// }
关键点:
User.prototype
是一个对象,用于存储所有实例共享的属性和方法。User.prototype.constructor === User
(始终成立)。
2. 实例与原型链
2.1 创建实例
使用 new
调用构造函数时,会创建一个新对象(实例),并设置其 __proto__
指向构造函数的 prototype
:
javascript
const user1 = new User("Alice");
const user2 = new User("Bob");
2.2 实例的 __proto__
实例的 __proto__
(即 [[Prototype]]
)指向构造函数的 prototype
:
javascript
console.log(user1.__proto__ === User.prototype); // true
console.log(user2.__proto__ === User.prototype); // true
图解关系:
javascript
user1.__proto__ → User.prototype
user2.__proto__ → User.prototype
User.prototype.constructor → User
3. 原型三角关系
3.1 核心等式
-
实例的
__proto__
指向构造函数的prototype
:javascriptuser1.__proto__ === User.prototype; // true
-
原型对象的
constructor
指向构造函数 :javascriptUser.prototype.constructor === User; // true
-
原型对象的
__proto__
指向Object.prototype
(因为User.prototype
本身是一个对象):javascriptUser.prototype.__proto__ === Object.prototype; // true
-
Object.prototype
是原型链的终点 :javascriptObject.prototype.__proto__ === null; // true
3.2 完整原型链
javascript
user1 → User.prototype → Object.prototype → null
4. 代码验证
4.1 在原型上添加方法
所有实例共享原型上的方法:
javascript
User.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
user1.greet(); // "Hello, my name is Alice"
user2.greet(); // "Hello, my name is Bob"
4.2 检查原型关系
javascript
console.log(user1 instanceof User); // true(检查原型链)
console.log(User.prototype.isPrototypeOf(user1)); // true(直接检查)
5. 常见误区澄清
5.1 误区 1:user1.prototype
存在
- 错误 :认为实例有
prototype
属性。 - 正确 :只有函数才有
prototype
,实例的__proto__
指向构造函数的prototype
。
5.2 误区 2:混淆 __proto__
和 prototype
__proto__
是实例的属性(非标准,但所有浏览器支持)。prototype
是函数的属性,用于构造实例的原型。
5.3 误区 3:直接修改 __proto__
虽然可以修改 __proto__
,但推荐使用 Object.create()
或 Object.setPrototypeOf()
:
javascript
// 不推荐(性能差)
user1.__proto__ = { ... };
// 推荐方式
const newProto = { ... };
Object.setPrototypeOf(user1, newProto);
6. 实际应用:继承
6.1 原型继承示例
javascript
function Admin(name, role) {
User.call(this, name); // 继承属性
this.role = role;
}
// 设置原型链
Admin.prototype = Object.create(User.prototype);
Admin.prototype.constructor = Admin;
// 添加方法
Admin.prototype.manage = function() {
console.log(`${this.name} is managing as ${this.role}`);
};
const admin = new Admin("Charlie", "Admin");
admin.greet(); // 继承自 User.prototype
admin.manage(); // "Charlie is managing as Admin"
6.2 ES6 Class 语法糖
javascript
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
class Admin extends User {
constructor(name, role) {
super(name);
this.role = role;
}
manage() {
console.log(`${this.name} is managing as ${this.role}`);
}
}
7. 总结
- 构造函数 :通过
new
创建实例,并关联prototype
。 - 原型对象 :存储共享属性和方法,
constructor
指向构造函数。 - 实例 :通过
__proto__
访问原型链。 - 原型链 :
实例 → 构造函数.prototype → Object.prototype → null
。
关键等式:
javascript
instance.__proto__ === Constructor.prototype;
Constructor.prototype.constructor === Constructor;
Constructor.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
理解这些关系后,你可以更高效地使用 JavaScript 的面向对象编程,并避免常见的原型继承陷阱! 🚀