原型及原型链

255 阅读14分钟

一、出现的原因

JavaScript是一个面向对象语言,即一切皆对象。

那么怎么生成对象?在Java的世界里,对象是由类(Class)实例出来的,通俗地说,就是将事物抽象成一个模具,用这个模具(类)生产出一个个具体的实物(对象)。

可是JS中没有类这个概念,有的是“原型”,对象是由原型衍生出来的。通俗地说,在JS的世界里,“原型”并不是一个模具,而是一个具体的实物(对象)。所有对象都是由另一个对象衍生出来的,而这个被衍生的对象就是所谓的“原型对象”。

JavaScript 中除了基础类型外的数据类型,都是对象(引用类型)。正是因为其没有 (class,ES6 引入了 class,但其只是语法糖)的概念,如何将所有对象联系起来就成立一个问题,于是就有了原型和原型链的概念。

二、基本概念

1.基本概念

  • js分为函数对象普通对象,每个对象都有__proto__属性(隐式原型),但是只有函数对象才有prototype属性(显式原型)。
  • Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String
  • 属性__proto__是一个对象,它有两个属性,constructor__proto__
  • 原型对象prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建,因为是对象自然也有__proto__属性

原型对象:每创建一个函数,该函数会自动带有一个prototype属性,该属性是一个指针,指向了一个对象,我们称之为原型对象。

补充理解:

  1. 实例对象a只有__proto__(隐式原型),构造函数既有 __proto__(隐式原型)也有prototype(显式原型)
  2. __proto__ 和 prototype 都是一个对象,既然是对象,就表示他们也有一个 __proto__
  3. 实例对象a的隐式原型指向它构造函数的显式原型,指向的意思是恒等于
  4. 当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。

原型链:通过__proto__向上进行查找,最终到null结束。 补充理解:

  1. 查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数的显式原型中也没有该属性,因为构造函数的显式原型也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
  2. 通过__proto__形成原型链而非protrotype

2.准则

//有以下构造函数Person,他的原型上有所属国属性motherland='China'
 function Person(name, age){ 
    this.name = name;
    this.age = age;
 }
 Person.prototype.motherland = 'China'
 
//通过new Person()创建的person01实例
let person01 = new Person('小明', 18);

js之父在设计js原型、原型链的时候遵从以下两个准则

  • 准则1:原型对象(即Person.prototype)的constructor指向构造函数本身,即Person.prototype.constructor == Person
  • 准则2:实例对象(即person01)的__proto__指向它的构造函数的原型对象(prototype ),即person01.__proto__ == Person.prototype

3.原型/原型链的关系

(1)整体图

1635934337(1).jpg 每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototyp:e )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

(2)经典的原型链相等图

image.png

// 从上方 function Foo() 开始分析这一张经典之图
function Foo()
let f1 = new Foo();
let f2 = new Foo();

f1.__proto__ = Foo.prototype; // 准则2
f2.__proto__ = Foo.prototype; // 准则2
Foo.prototype.__proto__ = Object.prototype; // 准则2 (Foo.prototype本质也是普通对象,可适用准则2)  
Object.prototype.__proto__ = null; // 原型链到此停止
Foo.prototype.constructor = Foo; // 准则1
Foo.__proto__ = Function.prototype; // 准则2
Function.prototype.__proto__  = Object.prototype; //  准则2 (Function.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
// **此处注意Foo 和 Function的区别, Foo是 Function的实例**

// 从中间 Function Object()开始分析这一张经典之图
function Object()
let o1 = new  Object();
let o2 = new  Object();

o1.__proto__ = Object.prototype; // 准则2
o2.__proto__ = Object.prototype; // 准则2
Object.prototype.__proto__ = null; // 原型链到此停止
Object.prototype.constructor = Object; // 准则1
Object.__proto__ = Function.prototype // 准则2 (Object本质也是函数);
// 此处有点绕,Object本质是函数,Function本质是对象
Function.prototype.__proto__ =  Object.prototype; // 准则2 (Function.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止

// 从下方 Function Function()开始分析这一张经典之图
function Function()
Function.__proto__ = Function.prototype // 准则2
Function.prototype.constructor = Function; // 准则1

以上一整个原型与原型层层相链接的过程即为原型链

此可以得出结论: 除了Object的原型对象(Object.prototype)的__proto__指向null,其他内置函数对象的原型对象(例如:Array.prototype)和自定义构造函数的 __proto__都指向Object.prototype, 因为原型对象本身是普通对象。 即:

Object.prototype.__proto__ = null;
Array.prototype.__proto__ = Object.prototype;
Foo.prototype.__proto__  = Object.prototype;

4.自引用设计

Function 构造函数的原型关系是 JavaScript 原型体系中一个非常特殊的「自引用」设计。

Function.__proto__ = Function.prototype
Function.prototype.constructor = Function

先回顾原型链的两个基础规则:

  1. 规则 1:任何原型对象(prototype)的 constructor 属性,默认指向其对应的构造函数。例如:Person.prototype.constructor === PersonPerson 是自定义构造函数)。
  2. 规则 2:任何对象(包括函数对象)的 __proto__ 属性,指向其「原型对象」(即创建它的构造函数的 prototype)。例如:const p = new Person(),则 p.__proto__ === Person.prototype
原因

为什么 Function 会有这种「自引用」? Function 是 JavaScript 中所有函数的「根构造函数」—— 所有函数(包括普通函数、箭头函数、内置函数如 ObjectArray 等)本质上都是通过 Function 构造函数创建的(或等价于其创建的实例)。

这种特殊性导致了 Function 的原型链出现了「自循环」:

  •  Function.prototype.constructor = Function(符合规则 1) Function.prototype 是 Function 构造函数的原型对象,根据规则 1,它的 constructor 属性自然指向其对应的构造函数 Function。这和普通构造函数的逻辑一致,例如 Person.prototype.constructor = Person
  •  Function.__proto__ = Function.prototype(看似打破规则,实则特殊设计) 根据规则 2,一个对象的 __proto__ 应指向创建它的构造函数的 prototype。但 Function 本身也是一个函数(函数是特殊的对象),那么谁是创建 Function 的构造函数?答案是:Function 自己。

因为 Function 是「根构造函数」,没有更上层的构造函数来创建它,所以 JavaScript 设计为:Function 是由自身创建的实例。因此: Function 的 __proto__ 指向其构造函数(自身)的 prototype,即 Function.__proto__ = Function.prototype

用通俗的话解释:

  • Function 是一个函数,同时也是一个构造函数。
  • 作为构造函数,它的原型对象是 Function.prototype,所以 Function.prototype.constructor 指向 Function(规则 1)。
  • 作为一个函数对象,它必须有一个原型(__proto__),但没有其他构造函数能创建它,所以它的原型只能指向自己的 prototypeFunction.prototype),形成自引用。
验证与扩展

所有函数的 __proto__ 都指向 Function.prototype(因为所有函数都是 Function 的实例):

function fn() {} 
console.log(fn.__proto__ === Function.prototype); // true 
console.log(Object.__proto__ === Function.prototype); // true(Object 也是函数)

而 Function 自身的 __proto__ 也指向 Function.prototype,形成了一个闭环:

console.log(Function.__proto__ === Function.prototype); // true

这种设计是 JavaScript 原型体系的底层特殊逻辑,目的是保证所有函数的原型链最终能统一指向 Function.prototype,同时避免「谁创造了根构造函数」的无限循环问题。

三、意义

原型对象的作用,是用来存放实例中共有的那部份属性、方法,可以大大减少内存消耗。

eg:

//有以下构造函数Person,他的原型上有所属国属性motherland='China'
 function Person(name, age){ 
    this.name = name;
    this.age = age;
 }
 Person.prototype.motherland = 'China'
 
//通过new Person()创建的person01,person02实例
let person01 = new Person('小明', 18);
let person02 = new Person('小花', 20);

console.log(person01)
console.log(person02)

打印person01, 他有自己属性 name = '小明',age = 18; 同时通过原型链关系,他有属性motherland = 'China';

打印person02, 他有自己属性 name = '小花',age = 20; 同时通过原型链关系,他有属性motherland = 'China';

原型对象存放了person01、person02共有的属性所属国motherland = 'China'. 我们不用在每个实例上添加motherland 属性,而是将这一属性存在他们的构造函数原型对象上。

当相同的属性、方法越多,原型、原型链的意义越大。 eg:

Person.prototype.hairColor = 'black';
Person.prototype.eat = function(){
    console.log('We usually eat three meals a day.')
}
console.log(person01)
console.log(person02)

此时我们再打印person01、person02,我们惊喜的发现,他们有了属性hairColor和eat方法;实例们动态的获得了Person构造函数之后添加的属性、方法,这是就是原型、原型链的意义所在!可以动态获取,可以节省内存。

注意:如果person01将头发染成了黄色,那么hairColor会是什么呢?

person01,hairColor = 'yellow';
console.log(person01)
console.log(person02)
复制代码

可以看到,person01的hairColor = 'yellow', 而person02的hairColor = 'black'; 实例对象重写原型上继承的属相、方法,相当于“属性覆盖、属性屏蔽”,这一操作不会改变原型上的属性、方法,自然也不会改变由统一构造函数创建的其他实例,只有修改原型对象上的属性、方法,才能改变其他实例通过原型链获得的属性、方法。

总结

构造函数、原型对象、实例对象三者分别是什么?

  • 构造函数:是用来创建对象的函数,通过new关键字来声明。
  • 原型对象:每一个函数在创建的时候,系统都会给分配一个对象,这个对象就是原型对象。
  • 实例对象:构造函数中通过new关键字返回的对象就是实例对象。

打个比方:三者就相当于一家三口的关系,构造函数相当于爸爸,原型对象相当于妈妈,实例对象相当于儿子,爸爸只能有一个妻子,妈妈只能有一个丈夫,所以它们是相互指向的,而儿子必须需要通过妈妈才能找到自己的亲爸爸(因为妈妈一定是亲的,爸爸就不一定咯),哈哈哈。通过这么一比喻是不是就更容易理解了呢!

普通对象、实例对象、原型对象三者分别是什么?

在 JavaScript 中,普通对象实例对象原型对象 是三个紧密关联但含义不同的概念,它们的核心区别体现在创建方式作用在原型链中的位置上。我们可以通过以下方式清晰区分:

(1)普通对象(Plain Object)

定义:最基础的对象类型,通常通过对象字面量 {}  或 new Object()  创建,不与特定的自定义构造函数绑定,是 “无类型标识” 的通用数据容器。

特点:

  • 创建方式{ key: value } 或 new Object()
  • 原型链:直接继承自 Object.prototype__proto__ === Object.prototype)。
  • 作用:用于存储无序键值对(如配置、状态等),无特定业务逻辑。
  • 无 “类型归属” :不属于任何自定义构造函数,仅属于 Object 类型。
const plainObj = { name: '普通对象' }; 
console.log(plainObj.__proto__ === Object.prototype); // true(原型是 Object 原型) console.log(plainObj instanceof Object); // true(是 Object 的实例) 
console.log(plainObj instanceof Person); // false(与自定义构造函数无关)
(2)实例对象(Instance Object)

定义:通过构造函数(new 构造函数()  或 class 实例化创建的对象,是某个特定构造函数 / 类的 “具体实例”,继承了该构造函数原型上的属性和方法。

特点:

  • 创建方式new 构造函数()(如 new Person()new Array())。
  • 原型链__proto__ 指向对应构造函数的 prototype(原型对象)。
  • 作用:承载特定业务逻辑(如用户、商品等实体),多个实例共享构造函数原型上的方法。
  • 有明确 “类型归属” :可通过 instanceof 检测所属构造函数。
// 自定义构造函数
function Person(name) {
  this.name = name;
}
// 原型上的方法(所有实例共享)
Person.prototype.sayHi = function() {};

// 实例对象(Person 的实例)
const person = new Person('张三');

console.log(person.__proto__ === Person.prototype); // true(原型是 Person 的原型对象)
console.log(person instanceof Person); // true(明确属于 Person 类型)
(3)原型对象(Prototype Object)

定义:每个函数(构造函数)  自带的 prototype 属性所指向的对象,是该构造函数所有实例的 “原型”,用于存储实例共享的属性和方法。

特点:

  • 创建方式:函数定义时自动创建(function F() {} 会自动生成 F.prototype)。
  • 核心作用:实现实例间的属性 / 方法共享(避免重复创建,节省内存)。
  • 自带 constructor 属性:默认指向对应的构造函数(F.prototype.constructor === F)。
  • 原型链位置:是实例对象的直接原型(实例.__proto__ === 构造函数.prototype)。
function Person() {}
// 原型对象(Person.prototype)
console.log(Person.prototype); // { constructor: ƒ Person(), __proto__: Object.prototype }

// 原型对象的 constructor 指向构造函数
console.log(Person.prototype.constructor === Person); // true

// 实例的原型就是原型对象
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
三者关系与原型链示意图
null(原型链终点)
  ↑ __proto__
普通对象的原型(Object.prototype)
  ↑ __proto__
原型对象(Person.prototype)
  ↑ __proto__
实例对象(p)
  • 实例对象的 __proto__ 指向原型对象(构造函数的 prototype)。
  • 原型对象本身也是一个 “普通对象”(继承自 Object.prototype)。
  • 所有对象最终都间接继承自 Object.prototype,除了 Object.prototype 自身(其 __proto__ 是 null)。

Object 和 Function 区别?

在 JavaScript 中,Object 和 Function 是两个核心的内置构造函数,它们既是函数(可用于创建实例),也是对象(可拥有属性和方法),但二者的定位和作用有本质区别。

定义:

  • Function:是所有函数的 “根构造函数”,负责创建和定义函数的行为。所有函数(包括普通函数、箭头函数、内置构造函数如 ObjectArray 等)本质上都是 Function 的实例。
  • Object:是所有对象的 “根构造函数”,负责定义对象的基础行为(如 toString()hasOwnProperty() 等)。所有对象(包括普通对象、数组、函数、原型对象等)最终都继承自 Object.prototype

核心作用:

  • Function 的核心作用:

    • 创建函数实例:通过 new Function(...) 可以动态创建函数(虽然实际开发中很少用,但理论上所有函数都等价于 Function 的实例)。示例:const fn = new Function('a', 'b', 'return a + b');(等价于 function fn(a, b) { return a + b })。
    • 定义函数的共享行为Function.prototype 上的方法(如 apply()call()bind())是所有函数都能共享的方法,用于控制函数的执行方式。
  • Object 的核心作用:

    • 创建普通对象:通过 new Object() 或对象字面量 {} 创建普通对象({} 是 new Object() 的语法糖)。
    • 定义对象的共享行为Object.prototype 上的方法(如 toString()hasOwnProperty()isPrototypeOf())是所有对象(包括函数对象)都能共享的基础方法。

原型链中的关系 Function 和 Object 之间存在特殊的 “互相依赖” 关系,这是 JavaScript 原型体系的底层设计:

  • Function 是 Object 的实例: 因为 Function 本身是一个对象(函数是特殊的对象),而所有对象最终都继承自 Object.prototype
    • Function 的本质:Function 是构造函数,同时也是一个「对象」(JavaScript 中函数是对象的子集)。
    • 所有对象的最终父级:任何对象(包括函数对象)的原型链最终都会指向 Object.prototype(对象的根原型)。
    • 原型链证据:Function 的原型链经过 Function.prototype 后,最终指向 Object.prototype
// Function 的 __proto__ 是 Function.prototype(第一步已讲)
// 而 Function.prototype 的 __proto__ 指向 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true
// 因此 Function 的原型链包含 Object.prototype,符合 instanceof 判定规则
console.log(Function instanceof Object); // true → 因此 Function 是 Object 的实例
  • Object 是 Function 的实例: 因为 Object 本身是一个函数(构造函数),而所有函数都是 Function 的实例

    • Object 的本质:Object 是 JavaScript 内置的构造函数(用于创建对象),它首先是一个「函数对象」。
    • 所有函数的创建者:在 JavaScript 中,任何函数(包括内置构造函数 ObjectArrayFunction 自身)都是 Function 构造函数的实例
    • 原型链证据:函数的 __proto__ 指向 Function.prototype(创建它的构造函数的原型)。
// Object 是函数,因此它的 __proto__ 指向 Function.prototype
console.log(Object.__proto__ === Function.prototype); // true
// 根据 instanceof 的判定规则:若 A.__proto__ 指向 B.prototype,则 A instanceof B 为 true
console.log(Object instanceof Function); // true → 因此 Object 是 Function 的

简单说:Function 创建了 Object 这个函数,而 Object 定义了 Function 这个对象的基础行为

一句话总结:

  • Function 是 “函数的源头”,负责生成所有函数并定义函数的行为;
  • Object 是 “对象的源头”,负责定义所有对象(包括函数)的基础行为。

用原型链闭环图理解

Function  →  __proto__ →  Function.prototype  →  __proto__ →  Object.prototype  →  __proto__ → null
  ↑                                                                                   ↑
  |                                                                                   |
  是 Function 的实例(因为 __proto__ 指向 Function.prototype)                          |
                                                                                      |
Object  →  __proto__ →  Function.prototype  →  __proto__ →  Object.prototype          |
  ↑                                                                                   |
  |                                                                                   |
  是 Function 的实例                                                                   |
                                                                                      |
  (所有函数都是 Function 的实例)                          (所有对象最终继承自 Object.prototype,因此都是 Object 的实例)