JavaScript 原型详解:从概念到实践
JavaScript 的面向对象编程与传统的类式面向对象不同,它基于原型(prototype) 实现。理解原型是掌握 JS 面向对象的核心,本文结合实例代码,详细解析原型的概念、工作原理及实践应用。
一、什么是原型?
在 JavaScript 中,每个函数都有一个特殊的属性prototype,它的值是一个对象(称为 "原型对象")。当通过new关键字调用函数(作为构造函数)创建实例时,实例会自动关联到构造函数的prototype对象,从而共享原型对象上的属性和方法。
用一句话概括:原型是构造函数的 "共享仓库",存储所有实例需要共享的属性和方法。
二、构造函数与原型的关系
构造函数是用于创建实例的 "模板",而原型对象则是构造函数为实例准备的 "共享资源"。两者的关系可以通过代码直观体现:
示例 1:基础构造函数与原型
javascript
// 定义构造函数(首字母大写,约定俗成)
function Person(name, age) {
// 实例自身的属性(每个实例独立拥有)
this.name = name;
this.age = age;
}
// 原型对象:所有实例共享的属性
Person.prototype.species = '人类';
// 创建实例
const persona1 = new Person('张三', 18);
const persona2 = new Person('金总', 19);
// 实例可访问自身属性和原型属性
console.log(persona1.name); // 张三(自身属性)
console.log(persona1.species); // 人类(原型属性)
console.log(persona2.species); // 人类(原型属性)
代码解释:
Person是构造函数,通过this为每个实例定义独立属性(name、age)。Person.prototype是原型对象,定义了共享属性species,所有Person实例都能访问。- 即使创建多个实例,
species属性只在原型中存储一份,实现了属性共享,节省内存。
三、实例与原型的关联:__proto__
每个实例对象都有一个私有属性__proto__(ES5 中可通过Object.getPrototypeOf()访问),它指向创建该实例的构造函数的prototype对象。这是实例能访问原型属性的核心原因。
示例 2:__proto__与prototype的关系
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
// 重写原型对象(注意:用对象字面量重写时需手动指定constructor)
Person.prototype = {
species: '人类',
sayHi: function() {
console.log(`你好,我是${this.name}`);
}
};
const su = new Person('舒老板', 19);
// 实例的__proto__指向构造函数的prototype
console.log(su.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(su) === Person.prototype); // true(更推荐的写法)
代码解释:
- 实例
su的__proto__属性直接指向Person.prototype,因此能访问species和sayHi。 Object.getPrototypeOf()是 ES5 规范中获取原型的方法,比__proto__更推荐使用。
图解:

四、原型链:属性查找的规则
当访问实例的某个属性时,JS 会先在实例自身查找;如果找不到,会沿着__proto__指向的原型对象查找;如果原型对象中也没有,会继续查找原型对象的原型(即prototype.__proto__),直到找到null(原型链的终点)。这就是原型链。
示例 3:原型链的查找机制
javascript
function Person(name) {
this.name = name;
}
// 原型对象1:Person.prototype
Person.prototype.species = '人类';
const su = new Person('舒老板');
// 1. 实例自身有name属性,直接返回
console.log(su.name); // 舒老板
// 2. 实例自身没有species,查找原型对象
console.log(su.species); // 人类
// 3. 实例和Person.prototype都没有toString,查找原型的原型(Object.prototype)
console.log(su.toString()); // [object Object](来自Object.prototype)
// 4. Object.prototype的原型是null,停止查找
console.log(Object.prototype.__proto__); // null
代码解释:
su.toString()的查找路径:su自身 →Person.prototype→Object.prototype(找到toString方法)。- 所有对象的原型链最终都会指向
Object.prototype,而Object.prototype.__proto__为null,标志着原型链的结束。
图解:

示例 4:实例属性覆盖原型属性
ini
function Person(name) {
this.name = name;
}
Person.prototype.species = '人类';
const su = new Person('舒老板');
su.species = 'LOL达人'; // 给实例添加同名属性
console.log(su.species); // LOL达人(优先访问实例自身属性)
console.log(su.__proto__.species); // 人类(原型属性未被修改)
代码解释:
- 当实例有与原型同名的属性时,会优先访问实例属性(不会修改原型属性)。
- 若要修改原型属性,需直接操作
构造函数.prototype.属性。
五、原型继承
通过修改构造函数的prototype指向另一个构造函数的实例,可以实现原型继承,让子类实例共享父类原型的属性和方法。
示例 5:原型继承实现
javascript
// 父构造函数
function Animal() {}
Animal.prototype.species = '动物';
// 子构造函数
function Person() {}
// 关键:让Person的原型指向Animal的实例,实现继承
Person.prototype = new Animal();
// 创建子类实例
const su = new Person();
console.log(su.species); // 动物(继承自Animal的原型)
console.log(su.__proto__); // Animal实例(Person.prototype)
console.log(su.__proto__.__proto__); // Animal.prototype(原型链的上一级)
代码解释:
Person.prototype = new Animal()让Person的原型成为Animal的实例,因此Person的实例su能通过原型链访问Animal.prototype上的species。- 原型继承是 JS 实现继承的基础,本质是通过原型链共享父类资源。
六、总结
-
原型核心概念:
- 构造函数的
prototype属性是原型对象,存储所有实例的共享属性 / 方法。 - 实例的
__proto__属性指向构造函数的prototype,是实例与原型的连接纽带。
- 构造函数的
-
原型链规则:
- 属性查找沿
__proto__链条向上,直至null。 - 实例属性优先于原型属性。
- 属性查找沿
-
实践价值:
- 通过原型实现属性 / 方法共享,减少内存消耗。
- 通过原型链实现继承,构建复杂的对象关系。