前言
在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两个对象都添加,这就是继承的好处。
而通过原型来存放实例中共有的那部份属性与方法,也可以大大减少内存消耗。