浅谈原型链-面试常考

72 阅读3分钟

原型的概念还是非常复杂且容易令人产生错误的,本文第一版就有很多错误概念,这里再修改后,就相对少多了 (应该吧,欢迎指出错误)

1. 原型概念

JS 是一门基于原型的语言,其他语言通过类描述和实例化对象,JS 通过构造函数描述和实例化对象,通过原型实现对象继承。

JS 中几乎每个对象都有隐式原型 __proto__,相当于他的父对象,但是基本没有显式原型 prototype (除了函数),这里的对象可以是普通的对象、也可以是函数等,他们都会继承有其他的东西

每个构造函数上都有一个显式原型,用于描述创建的对象的属性和行为,对象的隐式原型等于它构造函数的显式原型

当对象上找不到某个属性时,他就会往原型上寻找,原型上没有就会向原型的原型上寻找,这也就是原型链的基本概念

2. 基本原型三角

  • 创建一个函数 A,在 A 中使用 this 描述要创建的对象的属性和方法,prototype 描述在创建的对象上继承的属性和方法,修改 prototype 会影响所有实例化的对象
  • 实例化对象可以通过 contructor 获取到构造函数的引用,通过 __proto__ 获取到原型
  • 函数 A 的原型的构造函数实际上是函数 A 本身
function A() {}

const f1 = new A();

// __proto__ 的引用和 prototype 的引用是相等的,但是如果修改了prototype,__proto__ 的引用不会改变
f1.__proto__ === A.prototype; // true

// f本身是没有constructor属性的,它是通过原型链找到的
f1.__proto__.constructor === A; // true

// 如果修改prototype是一个有原型的对象,新的实例化对象的构造函数不指向A
function B() {}

A.prototype = {
  constructor: B,
};

const f2 = new A();

f2.__proto__.constructor === A; // false
f2.__proto__.constructor === B; // true
// 因为f1.__proto__ 还是原来那个对象,所以f1的构造函数还是A
f1.__proto__.constructor === A; // true

3. 对象原型三角

  • 任何一个没有其他继承关系的对象的隐式原型,都是 [Object: null prototype]{},而他的隐式原型最终会指向 null,这也就得到了原型链的终点 null
  • Object 构造函数的显式原型是 [Object: null prototype]{}
const obj1 = new Object()

obj1.__proto__ // [Object: null prototype] {}

obj1.__proto__ === Object.prototype // true

obj1.__proto__.__proto__ // null

obj1.__proto__.constructor === Object // true

注:在浏览器控制台可以打印对象结构,以下是各个对象的结构

  • Object
  • 实例化对象 obj1
  • obj1 的隐式原型/ Object 的显式原型

4. 函数原型三角

函数的构造函数和实例化函数之间的关系也比较特殊

  • Function 的显式原型和隐式原型是同一个函数,这个函数的 constructor 指向 Function
  • 实例化函数 f 默认自带一个显式原型,这和普通的实例化对象不一样,同时这个显式原型自带一个 constructor 指向 f
  • f 的隐式原型等于 Function 的原型

注:在浏览器控制台可以打印对象结构,以下是各个对象的结构

  • Function
  • 实例化函数 f
  • Function 的原型函数
  • f 的显式原型 prototype

5. 函数和对象的原型关系

除了函数本身的原型关系,它的原型和对象也有一定的原型关系

Function.prototype // f () { [native code] }

Function.__proto__ === Function.prototype // true

Function.__proto__.constructor === Function // true

Function.__proto__.__proto__ // [Object: null prototype] {}

Object.__proto__ // f () { [native code] }

Object.__proto__ === Function.prototype // true

Object.__proto__.constructor === Function // true

6. 原型的全貌

根据上面的分析,就可以得出原型的全貌图了,直接看的话还是太抽象了,可以拆开分析,使用代码验证。