原型与原型链详解

我们知道,在 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; // 手动修复
相关推荐
你的电影很有趣10 小时前
lesson71:Node.js与npm基础全攻略:2025年最新特性与实战指南
前端·npm·node.js
闲蛋小超人笑嘻嘻10 小时前
find数组方法详解||Vue3 + uni-app + Wot Design(wd-picker)使用自定义插槽内容写一个下拉选择器
前端·javascript·uni-app
小牛itbull10 小时前
初始化electron项目运行后报错 electron uninstall 解决方法
前端·javascript·electron
闲蛋小超人笑嘻嘻11 小时前
前端面试十四之webpack和vite有什么区别
前端·webpack·node.js
rggrgerj12 小时前
Vue3 组件完全指南代码
前端·javascript·vue.js
golang学习记13 小时前
从0死磕全栈之Next.js App Router动态路由详解:从入门到实战
前端
huangql52013 小时前
基于前端+Node.js 的 Markdown 笔记 PDF 导出系统完整实战
前端·笔记·node.js
在逃的吗喽13 小时前
Vue3新变化
前端·javascript·vue.js
yqwang_cn13 小时前
打造优雅的用户体验:自定义jQuery工具提示插件开发全解析
前端·jquery·ux
小Tomkk13 小时前
AI 提效:利用 AI 从前端 快速转型为UI/UX设计师和产品
前端·人工智能·ui