JavaScript | 原型&原型链

851 阅读4分钟

前言

本文首发于笔者的个人博客,欢迎围观。

如果在阅读过程发现文章有表达不当的地方,还希望各位大佬在评论区多多指教。

为什么存在原型、原型链

首先,我们先来看一个例子

function Person(name, age) {
  this.name = name
  this.age = age
  this.eat = function() {
    console.log(`${age}岁的${name}正在吃饭`)
  }
}

let person1 = new Person('aki', 19)
let person2 = new Person('aki', 19)

console.log(person1 === person2)		// false

为什么打印输出的结果是false呢?

因为对于同一个函数,我们通过 new 生成的实例,都会开辟出一个新的堆区,并且在每个实例上添加属性。所以在上述函数中,两个对象是不同的。

如果每生成一个对象都开辟一个新的堆区,很容易会造成内存不足的情况,而原型链&原型链就是来解决这个问题的,因为利用原型模式定义的属性和方法是有所有实例共享的,因此实例访问的都是相同的属性和函数。

我们不再需要在每个实例对象上添加属性,而是将属性添加在构造函数的原型对象上,这样一来,我们只需要通过原型链找到原型,实例对象就可以动态地获取构造函数的属性和方法。

如果上面那段话不是很好理解,那么接下来我将会通过例子理清各个概念以及它们之间的关系。

构造函数创建对象

先使用构造函数创建一个对象

function Person() {
	
}
let person1 = new Person()
person1.name = 'aki'
console.log(person1)

在这个例子中,Person 作为一个构造函数,创建了一个实例对象 person1,很好理解吧?

prototype

首先我们来讲讲prototype。在《JavaScript高级程序》一书中是这么说的:

无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)。

所以Person作为构造函数,也会有prototype属性指向实例原型,如下图所示。

2Nc1QU.png

那什么是原型呢?原型指的就是一个对象,实例“继承”那个对象的属性。在原型上定义的属性,通过“继承”,实例也拥有了这个属性。

“继承”这个行为是在 new 操作符内部实现的。

_proto_

每一个JavaScript对象(除了 null )都具有的一个隐式属性__proto____proto__ 属性会指向该对象的原型 prototype

可以用代码来证明这一点:

function Person() {
  
}
let person = new Person()
console.log(person.__proto__ === Person.prototype)	//true

如下图所示:

constructor

从上图不难发现,构造函数(函数)以及实例对象(对象)分别有一个 prototype 属性和 __proto__ 属性指向实例对象的原型。

那么,原型是否存在指向构造函数(函数)或者是实例对象(对象)的属性呢?答案是有的,不过原型只有指向构造函数的属性,毕竟一个构造函数能够生成很多实例对象。这个属性就是 constructor

依然用代码来证明这一点:

function Person() {
  
}
let person = new Person()
console.log(Person.prototype.constructor === Person)	//true

我们对上图进行补充,可以得到:

注意这里的 constructor 是原型的一个属性,Constructor 指的才是真正的构造函数。两者名字不要弄混了

理解原型

至此,我们已经了解了 构造函数实例对象实例原型 三者之间的关系:

每个构造函数都有一个原型对象prototype,原型对象都包含一个指向构造函数的指针constructor,而实例(instance)都包含一个指向原型对象的内部指针_proto_

多说无益,上代码:

/*
* 构造函数可以是函数表达式,也可以是函数声明。
* 所以下面两种形式都可以
* function Person() {}
* let Person = function() {}
* */
function Person() {}

/*
* 声明之后,构造函数就有一个与之关联的原型对象
* */
console.log(typeof Person.prototype)
console.log(Person.prototype)

/*
* 原型对象有一个 constructor 属性可以引用构造函数
* */
console.log(Person.prototype.constructor === Person)

/*
* 正常的原型链都会终止于 Object 的原型对象
* Object 原型的原型是 null
* */
console.log(Person.prototype.__proto__ === Object.prototype)
console.log(Person.prototype.__proto__.constructor === Object)
console.log(Person.prototype.__proto__.__proto__ === null)

/*
* 同一个构造函数创建的两个实例
* 共享同一个原型对象
* */
let person1 = new Person(),
    person2 = new Person()
console.log(person1.__proto__ === person2.__proto__)

/* 
 * instanceof可以检查实例的原型链中是否包含指定构造函数的原型
 * */
console.log(person1 instanceof Person)
console.log(person1 instanceof Object)
console.log(person1.prototype instanceof Object)

参考书籍及文章

《 JavaScript高级程序设计 》

冴羽 —— JavaScript深入之从原型到原型链

妖精的尾巴 —— 轻松理解JS 原型原型链