Skip to content

➕ 原型和原型链

🧬 原型

​ 在介绍原型的概念之前,让我们来看看原型所要解决的问题。如下图所示,通过构造函数User创建了三个实例,每个实例占用着单独的内存空间,它们的nameage属性是不同的,然而它们的writeName方法却完全相同。在这种情况下,相同逻辑的方法在内存中存在着三份,导致了空间的浪费。

原型解决的问题

​ 在JavaScript中,构造函数用于模拟面向对象编程中的类的概念。在大多数情况下,所属一类的不同实例的相同方法逻辑是相似的。基于此,为了避免在每个实例上都分配函数对象的内存空间,可将这些实例方法从实例对象中抽离出来,使得不同实例对象共享相同的函数对象。

tip: 在很多基于类的面向对象语言中,不同实例的相同方法总是共享同一块内存空间的,例如C++、Java。

​ 在JavaScript中,原型正是以上所述问题的解决方案。每个实例都有一个内置属性__proto__,属性指向的对象称为该实例的原型。

​ ==那实例的原型是从何而来呢?==默认情况下,实例的原型为其创建时对应构造函数的prototype属性值。每个函数都会自动附带一个属性prototype,它默认指向一个空对象。除部分内置函数外,prototype属性是可以重新赋值的。

​ 以构造函数User举例,具体关系图如下图所示。

原型关系图

tip:

  1. 有时候,__proto__也被称为隐式原型,prototype也被称为显式原型。
  2. 箭头函数没有自己的this值和原型,因此不能用作构造函数。

​ ==那如何实现不同实例共享相同的函数对象呢?==当访问实例成员时,如果实例本身不存在该成员,JavaScript引擎会自动从原型中查找。基于此,将公共成员放到构造函数的prototype属性对象中,该构造函数创建出的实例便能够共享这些成员。

tip:

  1. 实例成员指的是实例身上的属性和方法。
  2. 一般情况下,我们只需要将成员方法放入原型中即可。

🔗 原型链

​ 当通过构造函数创建实例后,实例的原型为构造函数的prototype属性值。而原型本身也是一个实例,默认情况下,它由构造函数Object创建产生。因此它也有原型,值为Object.prototype

​ 而Object.protytpe也是一个实例,==其是否也拥有原型呢?==答案是肯定的,它也存在__proto__属性,特殊的是,它的值为⭐null

基本原型链

​ 原型对象也会有自己的原型,逐渐构成了原型链。原型链终止于拥有null作为其原型的实例上,即Object.prototype。上图,User实例往上就是一条简单的原型链,也就是多个__proto__组成的链条。

​ 其实,当访问实例成员时,若成员在实例中不存在,”JavaScript引擎会自动从原型中查找"这种说法是不准确的。准确来说,JavaScript引擎会沿着实例的原型链向上查找,直到到达原型链的尽头为止,即null

tip:

  1. Object.prototype属性是只读的,其值不能修改,但支持添加新的成员。

  2. Object.prototype.__proto__是不可变的,值永远为null,当尝试修改时,会抛出错误。

​ 在JavaScript中,函数也是实例,它们都是通过构造函数Function创建而来。因此函数也都拥有原型,值为Function.prototype,⭐包括Function函数本身。

​ 而Function.prototype本身也是一个实例,它也是通过构造函数Object创建,所以其原型为Object.prototype

​ 下图展示了 JavaScript 中较完整的原型链结构。在这张图中,特别要注意粉色的线,因为它代表着一种特殊情况,需额外记忆和关注。

原型链

tip:

  1. Function.prototype属性是只读的,其值不能修改,但支持添加新的成员。
  2. Function.prototype.__proto__是可变的。这意味着在面向对象概念中,我们可以在FuctionObject中间加入继承类,从而构建更长的继承链。

🔌 API

  1. Object.create

​ 创建一个具有指定原型的新对象,并支持可选地为新对象添加指定的属性描述符。

Object.create

  1. Object.setPrototypeOf

​ 设置对象的原型。

Object.setPrototypeOf

  1. Object.getPrototypeOf

​ 返回对象的原型。

Object.getPrototypeOf


tip: 尽管大多数主流的 JavaScript 运行环境都支持使用__proto__属性来设置对象的原型,但我们也不推荐这样做。因为原型属性名是非标准的。在 ECMAScript 2015(ES6)之后的规范中,提供了Object.setPrototypeOfObject.getPrototypeOf来访问对象的原型。

👨‍👧‍👦 继承

上次更新于: