原型与原型链详解

我们知道,在 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; // 手动修复
相关推荐
BD_Marathon4 分钟前
IDEA中创建Maven Web项目
前端·maven·intellij-idea
waillyer10 分钟前
taro跳转路由取值
前端·javascript·taro
凌辰揽月21 分钟前
贴吧项目总结二
java·前端·css·css3·web
代码的余温22 分钟前
优化 CSS 性能
前端·css
在雨季等你36 分钟前
奋斗在创业路上的老开发
android·前端·后端
yume_sibai43 分钟前
Vue 生命周期
前端·javascript·vue.js
阿廖沙10241 小时前
前端不改后端、不开 Node,彻底搞定 Canvas 跨域下载 —— wsrv.nl 野路子实战指南
前端
讨厌吃蛋黄酥1 小时前
🌟 React Router Dom 终极指南:二级路由与 Outlet 的魔法之旅
前端·javascript
花颜yyds1 小时前
three.js学习
前端·three.js
SixHateSeven1 小时前
🚀 TSX动态编译的黑科技,快如闪电!
前端·编译器