JavaScript 原型与原型链

一. 核心概念

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.prototypeFunction.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 原型与闭包的区别?

  • 原型:方法 / 属性共享,内存占用少,适合通用方法;
  • 闭包:方法 / 属性私有化,每个实例独立,内存占用高,适合需隔离的场景。

总结

  1. 核心本质 :原型是实现 JavaScript 继承的核心机制,原型链是属性查找的链式结构,所有对象最终继承自 Object.prototype,终点为 null
  2. 最佳实践 :扩展方法优先用 Constructor.prototype,创建指定原型对象用 Object.create(),避免直接操作 __proto__
  3. 关键规则 :属性查找遵循 "自身优先、就近原则",原型方法的 this 指向实例,原型修改具有动态性且影响所有实例。
相关推荐
无风听海3 小时前
Python之TypeVar深入解析
开发语言·python·typevar
白中白121383 小时前
杂七杂八补充系列
开发语言·前端·javascript
佩奇大王3 小时前
P8 单词分析
java·开发语言
飞Link3 小时前
概率图模型的基石:隐马可夫模型 (HMM) 深度解析
开发语言·python·算法
PPPPickup3 小时前
小公司初面---java后端题目
java·开发语言·哈希算法
鸿乃江边鸟3 小时前
Rust 的 mod(模块) 说明
开发语言·后端·rust
敲代码的嘎仔3 小时前
Java后端开发——基础面试题汇总
java·开发语言·笔记·后端·学习·spring·中间件
赵谨言3 小时前
基于YOLOv5的火灾检测研究是当前计算机视觉和消防安全领域的重要研究方向
大数据·开发语言·经验分享·python
前端小D3 小时前
作用域/闭包
前端·javascript
前端 贾公子3 小时前
@uni-helper 社区:让 uni-app 拥抱 ESM 时代
开发语言·前端·javascript