原型与原型链详解

我们知道,在 JavaScript 中几乎所有数据都是对象(除 nullundefined),而我们想要掌握掌握其面向对象编程模型,那就要从理解原型(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

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

对比点 原型继承 类继承
语法 基于函数和原型对象 使用 classextends
是否语法糖 是(本质仍基于原型链)
构造函数调用方式 通过 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; // 手动修复
相关推荐
妄念鹿15 分钟前
记一次Uniapp的input输入框type为number时还能输入非数字
前端·javascript
ricardo197332 分钟前
浏览器渲染流水线:从 HTML 到屏幕上的像素
前端·面试
武当王丶也38 分钟前
React Native App 内更新实践:从版本策略到 APK 下载和安装
android·javascript·react native
明月_清风40 分钟前
2026 前端生存指南:8 个正在重塑你职业生涯的技术趋势
前端·ai编程
ZTStory1 小时前
Volta 新一代 node 版本管理工具
前端·javascript·node.js
用户938515635071 小时前
数组去重,从双重循环到一行 Set,我经历了什么?
javascript·算法
西索ovo1 小时前
揭开神秘面纱!JS 代码执行前竟暗藏玄机
javascript
不易_1 小时前
我的 AI 驱动开发工作流:基于 Cursor 的全流程实战开发方法论
前端·架构
许彰午1 小时前
32 个 Vue 组件的设计取舍
前端·javascript·vue.js
dfdvervdv1 小时前
Vue3 + Element Plus 表单校验踩坑:为什么我写的规则不生效?
前端