原型与原型链到底是什么?

一、原型(Prototype):对象的"基因库"

定义:原型是JavaScript对象内置的隐藏属性([[Prototype]]),用于实现属性和方法的继承与共享。

核心作用:让多个对象能够共享同一套属性/方法,避免重复定义,优化内存占用。

1. 原型的两种访问方式

实例对象属性:proto (ES6标准化,浏览器实现,不推荐直接操作)

构造函数属性:prototype(仅函数拥有,指向实例的原型对象)

推荐API:Object.getPrototypeOf(obj) 和 Object.setPrototypeOf(obj, proto)

javascript 复制代码
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true(实例.__proto__指向原型)
console.log(Object.prototype.constructor === Object); // true(原型.constructor指向构造函数)

2. 原型对象的特性

包含所有实例共享的属性/方法(如Array.prototype.push)

自带constructor属性,指向关联的构造函数

本身也是对象,因此也有自己的原型(形成原型链)

二、原型链(Prototype Chain):属性查找的"族谱"

定义:多个对象通过__proto__属性串联形成的链式结构,是JavaScript实现继承的核心机制。

1. 工作原理

当访问对象的属性/方法时:

  1. 先检查对象自身是否存在该属性
  2. 若不存在,沿__proto__向上查找原型对象
  3. 依次类推,直至找到属性或到达原型链终点(null)
javascript 复制代码
const arr = [1, 2, 3];
arr.toString(); // 实际调用 Object.prototype.toString

// 原型链路径:arr → Array.prototype → Object.prototype → null
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null(终点)

2. 原型链的典型结构

javascript 复制代码
实例对象  → 构造函数.prototype  → 父构造函数.prototype  → ... → Object.prototype → null
  arr     →  Array.prototype    →   Object.prototype     → ... →      null
  func    → Function.prototype  →   Object.prototype     → ... →      null

三、构造函数、原型与实例的"三角关系"

javascript 复制代码
// 1. 定义构造函数
function Person(name) {
  this.name = name; // 实例属性(每个实例独立拥有)
}

// 2. 在原型上定义共享方法
Person.prototype.sayHello = function() {
  console.log(`Hello, ${this.name}`); // this指向调用方法的实例
};

// 3. 创建实例
const person1 = new Person("Alice");
const person2 = new Person("Bob");

// 4. 关系验证
console.log(person1.__proto__ === Person.prototype); // true(实例→原型)
console.log(Person.prototype.constructor === Person); // true(原型→构造函数)
console.log(person1.constructor === Person); // true(实例继承constructor)

// 5. 共享方法调用
person1.sayHello(); // "Hello, Alice"(共享原型方法)
person2.sayHello(); // "Hello, Bob"

四、原型链的核心应用场景

1. 实现继承

组合继承(最经典的继承模式):

javascript 复制代码
function Student(name, major) {
  Person.call(this, name); // 继承实例属性
  this.major = major;
}

// 继承原型方法(关键步骤)
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student; // 修复constructor指向

// 添加子类独有方法
Student.prototype.study = function() {
  console.log(`${this.name} is studying ${this.major}`);
};

ES6 class语法糖(本质仍是原型继承):

javascript 复制代码
class Student extends Person {
  constructor(name, major) {
    super(name); // 等价于 Person.call(this, name)
    this.major = major;
  }
  study() { // 定义在Student.prototype上
    console.log(`${this.name} is studying ${this.major}`);
  }
}

2. 扩展内置对象功能

javascript 复制代码
// 为数组添加去重方法(谨慎使用,可能引发冲突)
Array.prototype.unique = function() {
  return [...new Set(this)];
};
[1, 2, 2, 3].unique(); // [1, 2, 3]

3. 创建纯净字典对象

javascript 复制代码
// 无原型链的对象,避免原型属性干扰
const safeMap = Object.create(null);
safeMap.toString = "自定义toString"; 
console.log(safeMap.toString); // "自定义toString"(不会访问Object.prototype.toString)

五、常见问题与避坑指南

1. 原型污染

风险:直接修改内置对象原型会影响所有实例

javascript 复制代码
// 危险行为!
Object.prototype.foo = "bar";
console.log({}.foo); // "bar"(所有对象都会继承foo属性)

解决方案:使用Object.create(null)创建隔离对象

2. 属性遮蔽(Shadowing)

实例属性会覆盖原型链上的同名属性:

javascript 复制代码
Person.prototype.age = 18;
const person = new Person("Alice");
person.age = 20; // 实例属性遮蔽原型属性
console.log(person.age); // 20(优先取实例属性)
delete person.age; // 删除实例属性后恢复原型属性
console.log(person.age); // 18

3. this指向问题

原型方法中的this指向调用该方法的实例:

javascript 复制代码
Person.prototype.getName = function() {
  return this.name;
};
const person = new Person("Alice");
const getName = person.getName;
console.log(getName()); // undefined(this指向window/global)

解决方案:使用箭头函数绑定或Function.prototype.bind()

六、调试与可视化工具

1. 控制台查看原型链:

console.dir(obj) 可展开对象完整原型链结构

2. 判断原型关系:

javascript 复制代码
// 方法1:instanceof(检查是否在原型链上)
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true

// 方法2:Object.prototype.isPrototypeOf()
console.log(Person.prototype.isPrototypeOf(person)); // true

3. 获取原型链路径:

javascript 复制代码
function getPrototypeChain(obj) {
  const chain = [];
  while (obj) {
    chain.push(obj.constructor.name || obj);
    obj = Object.getPrototypeOf(obj);
  }
  return chain;
}
getPrototypeChain([1,2,3]); // ["Array", "Object", "null"]

七、总结:原型链的本质与价值

本质:JavaScript通过原型链实现了对象间的属性继承,是一种不同于传统类继承的"原型继承"模式

价值:

  1. 实现代码复用与扩展
  2. 动态特性(运行时可修改原型)
  3. 轻量级对象模型

最佳实践:

  1. 优先使用class语法(更清晰的继承结构)
  2. 避免修改内置对象原型
  3. 复杂继承场景考虑使用组合优于继承的设计模式

理解原型与原型链是掌握JavaScript面向对象编程的关键,也是深入理解框架源码(如Vue的响应式原理)的基础。

相关推荐
m0_736927043 分钟前
2025高频Java后端场景题汇总(全年汇总版)
java·开发语言·经验分享·后端·面试·职场和发展·跳槽
宇余8 分钟前
从 useState 到 URLState:前端状态管理的另一种思路
前端·vue.js
白兰地空瓶14 分钟前
🚀 10 分钟吃透 CSS position 定位!从底层原理到避坑实战,搞定所有布局难题
前端·css
FAREWELL0007524 分钟前
Lua学习记录(3) --- Lua中的复杂数据类型_table
开发语言·学习·lua
T___T26 分钟前
Ajax 数据请求详解与实战
javascript·面试
onthewaying34 分钟前
在Android平台上使用Three.js优雅的加载3D模型
android·前端·three.js
IT北辰34 分钟前
Python实现居民供暖中暖气能耗数据可视化分析(文中含源码)
开发语言·python·信息可视化
冴羽40 分钟前
能让 GitHub 删除泄露的苹果源码还有 8000 多个相关仓库的 DMCA 是什么?
前端·javascript·react.js
悟能不能悟42 分钟前
jsp怎么拿到url参数
java·前端·javascript
KWTXX42 分钟前
组合逻辑和时序逻辑的区别
java·开发语言·人工智能