看完,你会发现,原型、原型链原来如此简单! | 七日打卡

1,898 阅读4分钟

前言

在Javascript(下面简称JS)的世界里,有这么一句话,一切皆为对象。

OMG,真的都是对象吗?那值类型(原始数据类型)也是对象???

答案:当然,不是。

准确来讲应该是对于 “引用类型” 而言,Javascript把数据类型分为原始数据类型和引用数据类型。

本文所讲的原型对象和原型链也只是针对对象才有的,JS以原型链的形式,保证对象中的方法、属性可以让向下传递,按照面向对象的说法,这就是继承。而JS通过原型链才得以实现对象的继承。(ES6之后有了extends关键字来实现继承)

在了解下面的内容前,我们要先大概了解下这四个概念:

  • JS内置了一些构造函数,如Object、Function、Array、Boolean、Number、String、Date等等。
  • JS把对象(除null)分为普通对象与函数对象,不管是什么对象都会有一个__proto__属性,函数对象还会有一个prototype属性,也就是说函数对象默认拥有__proto__属性与prototype属性。
  • __proto__属性是一个对象,它有两个属性,constructor__proto__
  • constructor属性,用于记录实例是由哪个构造函数创建的。

原型对象

看我标题,原型对象,所以原型其实指的也是一个对象,我们先把它理解为一个普通的 JS 对象即可。

我们先来看段简单的代码,看完也大致知道什么是原型了。

function Person() {}
var person = new Person();
console.log(Person.prototype, person.__proto__)
console.log(Person.prototype === person.__proto__)
console.log(Person === Person.prototype.constructor)
console.log(person.constructor === person.__proto__.constructor)

晕了吗?不要慌,根据上面代码我们大致能画出如下图:

通过上图的红箭头我们比较直观的知道,实例对象(person)能通过__proto__属性指向原型对象,构造函数能通过prototype属性也指向原型对象。

这里需要注意的是__proto__prototype这两个属性,__proto__不是一个规范的属性,只有部分浏览器实现了此属性,目前小编知道是谷歌与火狐浏览器能访问到此属性。而prototype属性是只函数才有的属性。

那原型对象到底是怎么来的呢? 你可以这样子理解:每个Javascript对象(null除外)在创建的时候就会关联另一个对象,这个对象就是我们所说的原型,每个对象都会从原型对象上继承属性。

原型链

原型大致讲完了,应该都能理解吧?怎么简单哦。。。。那么原型链呢?

其实聪明的小朋友可能发现了,既然对象有原型,那么对象的原型也是个对象,那在创建这个原型对象的时候,肯定也有与之关联的另一个对象才对,那这个原型对象是不是也有原型呢?(你品,你细品)

Bingo,只要是对象就有原型(除Object.prototype对象),所以原型对象也有属于自己的原型,通过原型关联起来的链状结构我们就称之为原型链。

我们用原型对象上的原型与Object构造函数的原型比较:

console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

所以我们大致改造图如下:

因为原型对象的原型也是一个对象,我们也能通过new Object()来创建,所以末端的原型链指到了Object.prototype,而最后为什么指向了null呢?根据阮一峰阮大神的意思是:null表示没有对象,即这里不应该有值,所以可以说Object.prototype没有原型。

所以上面的原型链完了吗? 当然没有。

我们知道了 对象 的原型链末端来自Object.prototype,那么 函数 的原型链末端不就能猜到来自Function.prototype了,包括Object()构造函数与Function()构造函数。(不要问我怎么猜到的。。。)

我们把图再改造一下: 看完图你懂了吗?你是不是聪明的小朋友呢?
我们先不看绿色的箭头用代码来验证下Function.prototype对象:

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

然后我们来说下三个绿色箭头的,三个箭头都是构造函数通过__proto__属性指向Function.prototype对象,前面我们就说过函数也是对象,所以函数也拥有__proto__,而函数的原型链末端会来到Function.prototype身上。

我们再代码验证下(没有代码验证的行为都是耍流氓):

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

作用

讲了那么多,那么原型、原型链到底有什么用呢?我想这才是大家关心的吧?其实前面就讲过了,就两个字“继承”。

我们直接来看下面的代码:

function Person(name) {
  this.name = name;
}
Person.prototype = {
  age: '十八',
  say() {
    console.log(this.age + '岁的' + this.name + '吃了三碗饭');
  }
}
var p1 = new Person('小明');
var p2 = new Person('小黄');

p1.say(); // 十八岁的小明吃了三碗饭
p2.say(); // 十八岁的小黄吃了三碗饭

我们能看到p1与p2都直接就继承了age属性与say()方法,我们不用单独去给p1与p2两个对象都添加,这就是继承的好处。

而通过原型来存放实例中共有的那部份属性与方法,也可以大大减少内存消耗。