前言
本文首发于笔者的个人博客,欢迎围观。
如果在阅读过程发现文章有表达不当的地方,还希望各位大佬在评论区多多指教。
为什么存在原型、原型链
首先,我们先来看一个例子
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属性指向实例原型,如下图所示。
那什么是原型呢?原型指的就是一个对象,实例“继承”那个对象的属性。在原型上定义的属性,通过“继承”,实例也拥有了这个属性。
“继承”这个行为是在 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高级程序设计 》