原型与原型链详解

我们知道,在 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; // 手动修复
相关推荐
lypzcgf19 小时前
Coze源码分析-工作空间-项目开发-前端源码
前端·人工智能·typescript·系统架构·开源软件·react·安全架构
yuguo.im19 小时前
Chrome DevTools Performance 是优化前端性能的瑞士军刀
前端·javascript·性能优化·chrome devtools
FSHOW21 小时前
【独立开发日记】MQ端到端类型安全
前端·javascript·后端
老华带你飞21 小时前
社区互助|基于SSM+vue的社区互助平台的设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·小程序·毕设·社区互助平台
一支鱼21 小时前
前端使用次数最多的工具封装
前端·typescript·编程语言
GIS之路21 小时前
GDAL 简介
前端
前端工作日常21 小时前
单元测试与E2E测试中使用浏览器的原因及区别
前端·单元测试
chxii1 天前
7.2elementplus的表单布局与模式
javascript·vue.js·elementui
Js_cold1 天前
Notepad++使用技巧1
前端·javascript·fpga开发·notepad++
接着奏乐接着舞。1 天前
前端RSA加密遇到Java后端解密失败的问题解决
java·开发语言·前端