一. 核心概念
1 原型(Prototype)
原型是 JavaScript 中实现对象继承的核心机制,本质是一个普通对象 。每个函数(含构造函数)在创建时会自动生成一个 prototype 属性,该属性指向这个原型对象;所有通过该构造函数创建的实例对象,都会共享原型对象上的属性和方法。
2 原型链(Prototype Chain)
当访问一个对象的属性 / 方法时,JavaScript 会先在对象自身查找;若找不到,则会沿着 [[Prototype]](内部原型)指向的原型对象继续查找,依次类推,直到找到目标属性 / 方法或到达原型链终点(null)。这种层层递进的链式查找结构,称为原型链。
3 核心关系
- 构造函数(Constructor):拥有
prototype属性,指向原型对象; - 原型对象(Prototype):拥有
constructor属性,指回构造函数; - 实例对象(Instance):通过
[[Prototype]](可通过Object.getPrototypeOf()获取)指向构造函数的prototype。
二. 设计初衷与核心价值(The "Why")
1 实现继承
原型链是 JavaScript 实现对象继承的唯一原生方式,让对象可以复用其他对象的属性和方法,避免代码冗余。例如:多个用户实例共享 "登录 / 退出" 方法。
2 节省内存
原型上的方法 / 属性只需在内存中存储一份,所有实例均可访问,而非为每个实例单独创建,大幅降低内存占用(尤其适合大量同类型对象场景)。
3 动态性
运行时修改原型对象,所有继承自该原型的实例会立即生效,无需重新创建实例。例如:给 Array.prototype 添加新方法,所有数组实例均可立即使用。
4 灵活的对象扩展
基于原型链可实现 "无类" 的灵活继承(JavaScript 无传统类继承,仅通过原型实现),支持多态、动态扩展等特性。
三、关键API/属性对照表
| API / 属性 | 作用 | 注意事项 |
|---|---|---|
[[Prototype]] |
对象内部原型,指向构造函数的 prototype |
仅引擎内部使用,外部不可直接访问 |
__proto__ |
浏览器暴露的 [[Prototype]] 访问器 |
非标准属性,性能差,易导致不可预测行为 |
Constructor.prototype |
构造函数的原型属性,供实例继承 | 所有构造函数默认自带,可直接扩展 |
Object.getPrototypeOf(obj) |
获取对象的 [[Prototype]] |
ES6 标准方法,安全可靠 |
Object.setPrototypeOf(obj, proto) |
设置对象的 [[Prototype]] |
频繁调用会导致性能问题 |
Object.create(proto, props) |
创建指定原型的新对象 | 纯原型继承的最佳方式 |
2 基础使用示例
示例 1:构造函数 + 原型扩展方法
javascript
// 1. 定义构造函数
function Person(name, age) {
// 实例自身属性
this.name = name;
this.age = age;
}
// 2. 原型上扩展共享方法(所有实例共享)
Person.prototype.sayHello = function() {
console.log(`Hello, 我是${this.name},今年${this.age}岁`);
};
Person.prototype.getAge = function() {
return this.age;
};
// 3. 创建实例
const alice = new Person("Alice", 20);
const bob = new Person("Bob", 22);
// 4. 实例访问原型方法
alice.sayHello(); // 输出:Hello, 我是Alice,今年20岁
bob.sayHello(); // 输出:Hello, 我是Bob,今年22岁
// 5. 验证原型关系
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
console.log(alice.constructor === Person); // true(原型的constructor指回构造函数)
示例 2:原型链结构验证
javascript
// 原型链层级:alice -> Person.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype) === null); // true
// 属性查找:自身无 -> 原型找 -> 顶层原型找
alice.toString(); // 实例无toString,从Object.prototype继承
示例 3:Object.create 实现纯原型继承
Object.create() 是 ES5 引入的核心 API,专为实现纯原型继承 设计,会 创建一个新对象 ,并将该新对象的 [[Prototype]](内部原型)显式指定为第一个参数;第二个可选参数用于定义新对象的自身属性(类似 Object.defineProperties 的配置)
javascript
// 1. 定义原型对象
const animalProto = {
eat: function() {
console.log(`${this.name}正在进食`);
}
};
// 2. 创建基于animalProto的实例
const cat = Object.create(animalProto, {
name: { value: "小花", writable: true }, // 自定义实例属性
type: { value: "猫" }
});
cat.eat(); // 输出:小花正在进食
console.log(Object.getPrototypeOf(cat) === animalProto); // true
示例 4:动态修改原型(体现原型动态性)
javascript
// 第一步:先定义构造函数 + 创建实例(补全基础代码)
function Person(name, age) {
this.name = name; // 实例自身属性
this.age = age; // 实例自身属性
}
// 初始原型方法
Person.prototype.sayHello = function() {
console.log(`Hello, 我是${this.name},今年${this.age}岁`);
};
// 创建实例(此时原型上还没有gender和getGender)
const alice = new Person("Alice", 20);
const bob = new Person("Bob", 22);
// 第二步:验证实例初始状态(无gender相关属性/方法)
console.log(alice.gender); // 输出:undefined(自身和原型都没有)
// alice.getGender(); // 直接调用会报错:getGender is not a function
// 第三步:动态修改原型(核心:实例创建后修改原型)
// 1. 新增原型属性和方法
Person.prototype.gender = "未知"; // 原型属性
Person.prototype.getGender = function() {
return this.gender; // this 指向调用实例
};
// 2. 重写原型方法
Person.prototype.sayHello = function() {
console.log(`Hi~ 我是${this.name}(原型方法已更新)`);
};
// 第四步:验证原型动态性(已创建的实例感知原型变化)
console.log(alice.gender); // 输出:未知(从原型继承)
console.log(alice.getGender()); // 输出:未知(调用原型方法)
alice.sayHello(); // 输出:Hi~ 我是Alice(原型方法已更新)
bob.sayHello(); // 输出:Hi~ 我是Bob(所有实例都生效)
// 补充:实例自身属性会遮蔽原型属性(关键细节)
alice.gender = "女"; // 给alice添加自身属性gender
console.log(alice.getGender()); // 输出:女(优先用自身属性)
console.log(bob.getGender()); // 输出:未知(仍用原型属性)
四. 关键注意事项
1 属性查找规则
- 优先查找对象自身属性,找不到才沿原型链查找;
- 若原型链中存在同名属性,就近原则(先找到的生效);
- 若整个原型链无该属性,返回
undefined(不会报错); - 给实例赋值属性时,只会修改实例自身,不会影响原型(除非显式修改原型)。
2 this 指向规则
原型方法中的 this 始终指向调用方法的实例对象,而非原型对象。
javascript
Person.prototype.updateAge = function(newAge) {
this.age = newAge; // this = 调用该方法的实例
};
alice.updateAge(25);
console.log(alice.age); // 25(alice的age被修改)
console.log(Person.prototype.age); // undefined(原型无age)
3 原型链终点与内置对象原型
- 所有内置对象(Array、String、Function 等)的原型链最终指向
Object.prototype; Object.prototype.__proto__ = null,是原型链的最终终点;- 函数的原型是
Function.prototype,Function.prototype.__proto__ = Object.prototype。
javascript
// 验证内置对象原型链
console.log(Object.getPrototypeOf([]) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype) === null); // true
五、实战
1 原型继承 vs 类继承(ES6 Class)
ES6 的 class 是原型继承的语法糖,本质仍基于原型链实现:
javascript
// ES6 Class 写法(等价于构造函数+原型)
class Person {
constructor(name) {
this.name = name;
}
sayHello() { // 等价于Person.prototype.sayHello
console.log(`Hello, ${this.name}`);
}
}
const tom = new Person("Tom");
console.log(Object.getPrototypeOf(tom) === Person.prototype); // true
2 如何实现多重继承?
JavaScript 不支持原生多重继承,但可通过 "原型混入(Mixin)" 实现:
javascript
// 定义多个原型对象
const walkProto = { walk: () => console.log("走路") };
const flyProto = { fly: () => console.log("飞行") };
// 混入原型
function mixin(target, ...sources) {
sources.forEach(source => {
Object.assign(target.prototype, source);
});
}
// 应用混入
function Bird() {}
mixin(Bird, walkProto, flyProto);
const sparrow = new Bird();
sparrow.walk(); // 走路
sparrow.fly(); // 飞行
3 原型与闭包的区别?
- 原型:方法 / 属性共享,内存占用少,适合通用方法;
- 闭包:方法 / 属性私有化,每个实例独立,内存占用高,适合需隔离的场景。
总结
- 核心本质 :原型是实现 JavaScript 继承的核心机制,原型链是属性查找的链式结构,所有对象最终继承自
Object.prototype,终点为null; - 最佳实践 :扩展方法优先用
Constructor.prototype,创建指定原型对象用Object.create(),避免直接操作__proto__; - 关键规则 :属性查找遵循 "自身优先、就近原则",原型方法的
this指向实例,原型修改具有动态性且影响所有实例。