原型
每一个JavaScript对象(null除外)都和另一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。——《JavaScript权威指南》
每创建一个对象,对象上就会有一个[[prototype]]属性指向该对象的原型,但因为[[prototype]]是内置属性,JavaScript无法直接进行访问,所以有些浏览器自己实现了__proto__属性让我们能够直接访问到对象的原型,在ES5之后,添加了Object.getPrototypeOf()函数来让我们能直接获取指定对象的原型。
- 当使用对象字面量来创建对象时,[[prototype]]指向Object.prototype
var obj = {}
console.log(Object.getPrototypeOf(obj)=== Object.prototype) // true
上例可以看到,使用字面量来创建对象,那么创建出的对象的原型和Object的prototype指向的是同一块内存区域。
- 当使用Object.create()来创建对象时,对象的原型就是指定的对象。
var obj = {a:1}
var obj2 = Object.create(obj)
console.log(Object.getPrototypeOf(obj)=== Object.prototype) // true
console.log(Object.getPrototypeOf(obj2) === obj) // true
上例可以看到,使用Object.create创建对象,那么创建出的对象的原型指向的是Object.create()中第一个参数(当第一个参数为null的时候,创建出来的是一个没有原型的空对象)
- 当使用new来创建对象时,对象的原型就是构造函数的原型
function Foo() {}
Foo.prototype.add = function(){}
var fn = new Foo()
fn.a = 1
console.log(Object.getPrototypeOf(fn) === Foo.prototype)
上例可以看到,使用new来创建对象,那么对象的原型指向的就是构造函数的原型。
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。——《JavaScript高级程序设计》
上面介绍了几种原型产生的方式,下面来介绍一下原型链是什么,以及原型链的作用。
原型链
JavaScript主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点和基于类的继承很相似。——《JavaScript高级程序设计》
通过构造函数来创建原型链
我们前面说过,使用new创建出来的对象会有一个[[prototype]],指向构造函数的prototype,所以我们可以利用这一点来创建出我们需要的原型链。
function Animal() {}
Animal.prototype.sing = function () {
console.log(this.voice)
}
function Dog() {
this.voice = '汪汪汪'
}
Dog.prototype = new Animal()
Dog.prototype.run = function () {
console.log("start running")
}
function Bird() {
this.voice = '啾啾啾'
}
Bird.prototype = new Animal()
Bird.prototype.fly = function () {
console.log("start flying")
}
var dog = new Dog()
var bird = new Bird()
上例定义了三个类型,Animal、Dog和Bird。Animal的prototype上有一个sing方法,Dog和Bird都有一个voice属性并且prototype上都有一个Animal的实例,Dog的prototype上有一个run方法,Bird的prototype上有一个fly方法。
下面看图解:
由图可知,构造函数Dog中定义在this上的属性会产生在实例dog上,实例dog的[[prototype]]指向构造函数Dog的原型,而构造函数Dog的[[prototype]]指向了Animal的原型。当实例dog想要调用sing方法的时候,先查找自身是否具有这个方法,没有的话就通过[[prototype]]到Dog的原型上进行查找,在Dog的原型中没有找到,那就再通过Dog的原型上的[[prototype]]到Animal的原型上进行查找,终于在Animal的原型中找到了这个方法,然后就可以开始调用sing方法了。
通过Object.create来创建原型链
我们使用Object.create来创建一个和上面一样效果的原型链:
var Animal = {
sing: function () {
console.log(this.voice)
}
}
var Dog = Object.create(Animal)
Dog.voice = '汪汪汪',
Dog.run = function () {
console.log('start running')
}
var Bird = Object.create(Animal)
Bird.voice = '啾啾啾'
Bird.fly = function () {
console.log('start flying')
}
var dog = Object.create(Dog)
dog.sing() // 汪汪汪
var bird = Object.create(Bird)
Bird.sing() // 啾啾啾
比较两幅图解可以看出,使用Object.create创建出的原型链明显要比使用构造函数来创建出的原型链简单直观很多,所以在我个人看来比较喜欢使用Object这样来构建需要的原型链。
原型的终点
所有原型链都会指向Object.prototype,而Object.prototype的[[prototype]]为null,所以原型链的终点就是null