(六) js中的原型与原型链

导言

函数也是对象, 所以当我们提到 函数或构造函数 时, 要能想到这一点


1. 原型 (prototype)

1.1 函数的prototype属性

  • 每个函数都有一个 prototype属性 , 它默认指向一个 , 的Object对象 (称为原型对象)

    注: 这里的 , 是说没有我们自定义的属性和方法

  • 原型对象中有一个属性 constructor, 它指向该原型对象的构造函数

    实例的构造函数属性(constructor)指向构造函数。

// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype);	// {}  'object'

function fn() { }
console.log(fn.prototype, fn.prototype.constructor === fn);		// {} true

// 默认指向一个Object空对象(没有我们的属性)
console.log(Function.prototype);	// {}

// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Function.prototype.constructor === Function);	// {}  true

1.2 为原型对象添加属性

给原型对象添加属性, 作用是可以让所有的实例对象共享原型对象中的属性和方法

function Obj() { }
Obj.prototype.sing = function () {
  console.log('i can sing');
}
const obj1 = new Obj()
const obj2 = new Obj()
obj1.sing()   // i can sing
obj2.sing()   // i can sing

对于构造函数而言, 每个自定义方法都要在每个实例里面重新创建一遍, 即: 每个Obj实例中都包含一个不同的Function实例, 但是这种方式会导致不同的作用域链和一些其他问题, 另外显而易见的是, 内存造成了浪费 !

而通过为原型对象添加属性的方式, 可以避免这一问题, 至于为什么,接着看完文章~


2.显式原型与隐式原型

2.1 prototype与__proto__

  • 每个函数都有一个 prototype属性 , 即: 显式原型(属性)

  • 每个实例对象(对象)都有一个__proto__属性, 即: 隐式原型(属性)

    实例也是对象, 也就是说: 每个对象都有一个__proto__属性

  • 实例对象的隐式原型的值为其对应的构造函数的显式原型的值

以上三句话要牢记

function Obj() { }	// 内部语句: this.prototype = {}

// 1. 每个函数都有一个prototype属性, 即显式原型(属性)
console.log(Obj.prototype);

// 2. 每个实例对象都有一个__proto__属性, 即隐式原型(属性)
const obj = new Obj()	// 内部语句: this.__proto__ = Obj.prototype
console.log(obj.);

// 3. 函数也是对象
console.log(Obj.__proto__);

// 4. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(obj.__proto__ === Obj.prototype);   // true

简单示意图:

2.2 属性查找规则

当实例调用方法时, 现在自身属性中查找, 找不到则从__proto__中查找

function Persion() {
  this.test = function () {
    console.log('Persion test');
  }
}

Persion.prototype.test = function () {
  console.log('prototype test');
}

Persion.prototype.eat = function () {
  console.log('prototype eat');
}
let p = new Persion()

p.test()    // Persion test
p.eat()     // prototype eat

3. 原型链

3.1 代码引入

先看几段简单的代码:

function Person(){ }

const p = new Person() 

const obj = new Object()

我们分别解析这两行代码,然后引出原型链的概念

先把上面几句重要的结论拿下来:

  • 每个函数都有一个 prototype属性 , 它默认指向一个 , 的Object对象 (称为原型对象)
  • 每个对象都有一个__proto__属性, 它指向构造函数的显式原型对象
  • 函数也是对象, 因此每个函数都有__proto__属性
  • 对象的隐式原型的值为其对应的构造函数的显式原型的值

当我们分析的时候, 脑海里要有以上几个结论

3.2 function Person()

function是定义函数的一种方式, 因此Person是一个函数, 而在Js中, 以大写字母开头的函数被称为构造函数,以此与普通函数做区分, 函数也是一个对象, 因此:

  • Person的prototype属性指向Person的显式原型对象 (Person.prototype)
  • Person的__proto__属性指向Person的构造函数的显式原型对象, 而所有的函数都是Function类型的实例, 因此 Person的__proto__属性指向Function的显式原型对象 (Function.prototype)
  • 既然所有的函数都是Function类型的实例, 那么Function函数也是它本身的实例吗 ? 结果为true
  • image-20210602144427747
  • Function的显式原型对象的__proto__指向谁 ?

3.3 const obj = new Object()

  • obj这个实例对象的__proto__属性指向构造函数Object的显式原型 (Object.prototype)

  • 既然Object()是一个构造函数 ,那么其__proto__属性(函数也是对象)指向Function的显式原型对象 (Function.prototype)

  • 我们知道, 在JavaScript中,几乎所有的对象都是Object类型的实例(Object.prototype除外),也就意味着Object()是除Object.prototype外所有对象的构造函数, 这也就解决了上面的问题: Function的显式原型对象的__proto__指向Object的显式原型对象(Object.prototype)

  • 而显然,Object.prototype作为一个对象,必然有一个__proto__属性, 那这个__proto__属性指向谁?

  • image-20210602152146239

  • 是null值

3.4 const p = new Person()

  • 实例对象p的__proto__属性指向构造函数Person的显式原型对象 (Person.prototype)
  • 同Object.prototype, Person.prototype也应有一个__proto__属性, 这个属性指向谁?
  • 我们上面提到: Object()是除Object.prototype外所有对象的构造函数, 因此Person.prototype也应有一个__proto__属性指向Object的显式原型对象 (Object.prototype)

3.5 原型链

把上面的一连串分析,串联起来,就形成了所谓的 原型链, 如图所示:


当创建函数时,JS会为这个函数自动添加prototype属性,值是空对象 值是一个有构造函数的对象,不是空对象

posted @ 2021-07-26 14:15  只猫  阅读(118)  评论(0编辑  收藏  举报