原型与原型链详解

我们知道,在 JavaScript 中几乎所有数据都是对象(除 null 和 undefined),而我们想要掌握掌握其面向对象编程模型,那就要从理解原型(Prototype)和原型链(Prototype Chain)开始。

1. 原型(prototype)

根据 ECMAScript 规范(ECMA-262),JavaScript 中的每个对象(Object)都有一个内部槽 [[Prototype]],它指向另一个对象或 null。这个 [[Prototype]] 在 JavaScript 中通过 __proto__ 或 Object.getPrototypeOf() 访问。

以下是es规范给的描述

4.4.8 prototype object that provides shared properties for other objects(给其它对象提供共享属性的对象)

1. 显式原型和隐式原型

在我们熟知的原型中除了 prototype,还有__proto__现代规范推荐使用 Object.getPrototypeOf()暴露出来),我们通常称prototype为显式原型,称__proto__为隐式原型。

2. 原型的作用

  • 共享属性和方法:多个对象实例可以共享同一个原型中的方法和属性,避免重复创建。
  • 实现继承机制:JavaScript 通过原型链模拟面向对象语言中的继承。
  • 属性查找机制的基础 :当访问一个对象的属性时,如果该对象自身没有,会沿着原型链向上查找,直到找到或为 null(查找结束)。

3. 原型的重要性

  • 是构建对象之间关联和继承的基础。
  • 提高内存效率(方法不重复创建)。
  • 是理解 JavaScript 面向对象模型的关键。

4. 如何创建和使用原型

4.1 使用构造函数与 prototype
javascript 复制代码
// 构造函数
function Parent(name) {
  this.name = name;
}

// 在 prototype 上添加方法(所有实例共享)
Parent.prototype.sayHello = function() {
  console.log(`你好,我是 ${this.name}`);
};

// 创建实例
const p1 = new Parent("小明");
const p2 = new Parent("小红");

p1.sayHello(); // 输出:你好,我是 小明
p2.sayHello(); // 输出:你好,我是 小红

// 验证共享
console.log(p1.sayHello === p2.sayHello); // true(两个实例共享同一方法)
4.2 对象字面量中的原型
javascript 复制代码
const parent = {
  greet() {
    console.log("Hello from parent");
  }
};

const child = Object.create(parent); // 创建一个以 parent 为原型的对象
child.name = "Child";

child.greet(); // 输出:Hello from parent
console.log(child.__proto__ === parent); // true
4.3 使用 Object.setPrototypeOf() 设置原型
javascript 复制代码
const proto = { sayHi() { console.log("Hi!"); } };
const obj = {};

Object.setPrototypeOf(obj, proto);

obj.sayHi(); // 输出:Hi!

2. 构造函数、原型对象与实例三者关系

javascript 复制代码
function Parent(name) {
  this.name = name;
}

const p1 = new Parent("小明");

console.log(p1.__proto__ === Parent.prototype); // true
console.log(Parent.prototype.constructor === Parent); // true
  • Parent.prototype: 构造函数的原型对象。
  • p1.__proto__: 实例对象的内部 [[Prototype]],也就是 Object.getPrototypeOf(p1)。
  • Parent.prototype.constructor: 指向构造函数本身,默认存在。

2. 什么是原型链

原型链是一种委托机制 ,多个对象通过 [[Prototype]] 层层相连,就形成了一条链式结构,这条链被称为 原型链(Prototype Chain) 。当我们访问对象的属性时,如果对象本身没有,会沿着 [[Prototype]] 一直向上查找,直到找到或到达 null。

javascript 复制代码
obj --> obj.__proto__ --> obj.__proto__.__proto__ --> ... --> null

1. 如何创建和使用原型链

1.1 默认的原型链结构(构造函数)
javascript 复制代码
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHello = function() {
  console.log('Hello, 我是' + this.name);
};

const child = new Parent('小明');
child.sayHello(); // Hello, 我是小明
  • child 没有 sayHello 方法;
  • 引擎会在 child.__proto__(即 Parent.prototype)中查找;
  • 找到了并执行。

原型链结构展示

javascript 复制代码
child --> child.__proto__(Parent.prototype) --> child.__proto__.__proto__(Object.prototype )--> null
1.2 使用 Object.create() 手动创建原型链
javascript 复制代码
const parent = {
  greet() {
    console.log("我是parent");
  }
};

const child = Object.create(parent);
child.name = "child";

child.greet(); // 我是parent
1.3 原型链的终点

原型链的尽头是 Object.prototype.__proto__ === null。即:所有对象最终都继承自 Object.prototype。

javascript 复制代码
console.log(Object.prototype.__proto__); // null

2. 图解原型链

javascript 复制代码
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHello = function() {
  console.log('Hello, 我是' + this.name);
};
const child = new Parent('小明');

3. 原型继承与类继承

1. 原型继承(Prototype Inheritance)

1.1 使用构造函数 + 原型对象
JavaScript 复制代码
function Animal(name) {
  this.name = name;
}
Animal.prototype.sayName = function () {
  console.log('我是' + this.name);
};

function Dog(name, breed) {
  Animal.call(this, name); // 继承属性
  this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 继承方法
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log(this.name + '旺旺');
};

const dog = new Dog('小狗');
dog.sayName(); // 输出: 我是小狗
dog.bark();    // 输出: 小狗旺旺
1.2 使用 Object.create()(纯粹的原型继承)
javascript 复制代码
const animal = {
  sayName() {
    console.log('我是' + this.name);
  }
};

const dog = Object.create(animal);
dog.name = '小狗';
dog.bark = function () {
  console.log(this.name + '旺旺');
};

dog.sayName(); // 输出: 我是小狗
dog.bark();    // 输出: 小狗旺旺

2 .类继承(Class Inheritance)

javascript 复制代码
class Animal {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(`我是${this.name}`);
  }
}

class Dog extends Animal {
  constructor(name, barking) {
    super(name); // 调用父类构造函数
    this.barking = '旺旺';
  }
  bark() {
    console.log(`${this.name}${this.barking}`);
  }
}

const dog = new Dog('小狗', '旺旺');
dog.sayName();  // 我是小狗
dog.bark(); // 小狗旺旺

// 虽然 ES6 引入了 `class`,但本质上仍然是基于原型实现的
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.__proto__.__proto__ === Animal.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
console.log(Dog.__proto__ === Animal); // true
console.log(Animal.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true

通过以上可以知道他们的异同

对比点 原型继承 类继承
语法 基于函数和原型对象 使用 class 与 extends
是否语法糖 否 是(本质仍基于原型链)
构造函数调用方式 通过 new,但无法传参到父构造函数 super() 直接调用父构造函数
继承内置对象 麻烦且容易出错 更简单且规范
可读性与维护性 差一些 好很多
原型链本质 显式设置原型 隐式通过 extends 设置原型链

4. 如何判断原型关系?

1. instanceof 运算符

javascript 复制代码
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHello = function() {
  console.log('Hello, 我是' + this.name);
};

const child = new Parent('小明');

console.log(child instanceof Parent); // true

2. isPrototypeOf 方法

javascript 复制代码
console.log(Parent.prototype.isPrototypeOf(child)); // true

5. 手写一个 instanceof

javascript 复制代码
function myInstanceof(obj, constructor) {
  let proto = Object.getPrototypeOf(obj);
  const prototype = constructor.prototype;

  while (proto) {
    if (proto === prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}
// myInstanceof(child,Parent) // true
// myInstanceof(Parent,Object) // true

6. 常见误区与陷阱

1. 原型污染与防范

javascript 复制代码
// 危险:修改原型影响所有对象
Object.prototype.admin = true;

const user = {};
console.log(user.admin); // true

// 防范方案:
// 1. 冻结原型
Object.freeze(Object.prototype);

// 2. 使用无原型对象
const safeDict = Object.create(null);

2. 原型赋值的坑

javascript 复制代码
function A() {}
A.prototype = {
  say() {
    console.log("hi");
  },
};

const a = new A();
console.log(a.constructor); // Object, 不是 A

解决办法:

javascript 复制代码
A.prototype.constructor = A; // 手动修复
相关推荐
鲤鱼_5993 小时前
教培机构一体化运营管理系统操作手册
前端
小白学大数据3 小时前
逆向分析短视频平台 a_bogus 参数:从 JavaScript 混淆到 Python 复现
javascript·python·音视频
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 3 章 Fiber 架构与双缓冲
前端·react.js·开源
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 9 章(下)lazy、retry、hydration 与实战
前端·react.js·开源
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 6 章 React DOM 渲染器与 Commit 阶段(把 Fiber 副作用落到真实 DOM)(上)
前端·react.js·开源
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 6 章 React DOM 渲染器与 Commit 阶段(把 Fiber 副作用落到真实 DOM)(下)
前端·react.js·开源
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 13 章:React 19 新特性深解
前端·react.js·开源
donk13143 小时前
React Native for OpenHarmony 实战:三方库 react-native-circular-action-menu 的鸿蒙化适配指南
javascript·分布式·react native·react.js·wpf·harmonyos
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 1 章 总览与架构哲学
前端·react.js·开源
鶴哥只手遮天3 小时前
React 19.3.1 源码深度剖析-第 4 章 Reconciliation 协调算法(Diff 算法全解)
前端·react.js·开源