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

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

定义 :原型是JavaScript对象内置的隐藏属性([[Prototype]]),用于实现属性和方法的继承与共享。
核心作用:让多个对象能够共享同一套属性/方法,避免重复定义,优化内存占用。

1. 原型的两种访问方式
  • 实例对象属性__proto__(ES6标准化,浏览器实现,不推荐直接操作)
  • 构造函数属性prototype(仅函数拥有,指向实例的原型对象)
  • 推荐APIObject.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语法糖(本质仍是原型继承):

scala 复制代码
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. 创建纯净字典对象
ini 复制代码
// 无原型链的对象,避免原型属性干扰
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)

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

ini 复制代码
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指向调用该方法的实例:

ini 复制代码
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. 获取原型链路径

    ini 复制代码
    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. 轻量级对象模型
  • 最佳实践

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

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

相关推荐
前端小趴菜0514 分钟前
React-React.memo-props比较机制
前端·javascript·react.js
RadiumAg3 小时前
记一道有趣的面试题
前端·javascript
yangzhi_emo3 小时前
ES6笔记2
开发语言·前端·javascript
yanlele3 小时前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
烛阴5 小时前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
初遇你时动了情5 小时前
腾讯地图 vue3 使用 封装 地图组件
javascript·vue.js·腾讯地图
dssxyz5 小时前
uniapp打包微信小程序主包过大问题_uniapp 微信小程序时主包太大和vendor.js过大
javascript·微信小程序·uni-app
ohMyGod_1237 小时前
React16,17,18,19新特性更新对比
前端·javascript·react.js
@大迁世界7 小时前
第1章 React组件开发基础
前端·javascript·react.js·前端框架·ecmascript
一瓣橙子7 小时前
7.7日 实验03-Spark批处理开发(2)
开发语言·javascript·ajax