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

一、原型(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的响应式原理)的基础。

相关推荐
paishishaba3 分钟前
处理Web请求路径参数
java·开发语言·后端
七七七七074 分钟前
C++类对象多态底层原理及扩展问题
开发语言·c++
神仙别闹5 分钟前
基于Java+MySQL实现(Web)可扩展的程序在线评测系统
java·前端·mysql
心.c20 分钟前
react当中的this指向
前端·javascript·react.js
Java水解27 分钟前
Web API基础
前端
闲鱼不闲28 分钟前
实现iframe重定向通知父级页面跳转
前端
咸鱼青菜好好味28 分钟前
node的项目实战相关-2-前台接口
前端
春秋半夏30 分钟前
音乐播放、歌词滚动
前端·css
Sioncovy33 分钟前
Zustand 源码阅读计划(3)- JS 篇 - Middlewares 中间件逻辑
前端·javascript
bo5210035 分钟前
垃圾回收机制详解
前端