大多数编程语言中,都有类和对象,一个类可以继承其他类。
在JavaScript中,继承是基于原型的(prototype-based),这意味着JavaScript中没有类,取而代之的是一个对象继承另一个对象。:)
1. 继承, the proto
在JavaScript中,当一个对象rabbit继承另一了对象animal时,这意味着rabbit对象中将会有一个特殊的属性:rabbit.__proto__ = animal;
当访问rabbit对象时,如果解释器在rabbit中不能找到属性,那么它会顺着__proto__链往上在animal对象中寻找
栗子中的__proto__属性仅在Chrome和FireFox中可以访问,请看一个栗子:
var animal = { eats: true } var rabbit = { jumps: true } rabbit.__proto__ = animal // inherit alert(rabbit.eats) // true
eats属性是从animal对象中访问的。
如果在rabbit对象中已经发现了属性,那么就不会去检查proto属性啦。
再来一个栗子,当子类中也有eats属性时,父类中的就不会访问了。
var animal = { eats: true } var fedUpRabbit = { eats: false} fedUpRabbit.__proto__ = animal alert(fedUpRabbit.eats) // false
你也可以在animal中添加一个函数,那么在rabbit中也可以访问了。
var animal = { eat: function() { alert( "I'm full" ) this.full = true } } var rabbit = { jump: function() { /* something */ } } rabbit.__proto__ = animal
(1)rabbit.eat():
rabbit.eat()函数以如下两步执行:
首先,解释器查找rabbit.eat,rabbit中没有eat函数,那么它就顺着rabbit.__proto__往上找,在animal中找到了。
函数以this = rabbit运行。this值与__proto__属性完全无关。
因此,this.full = true在rabbit中:
看看这里我们有什么新发现,一个对象调用了父类函数,但是this还是指向对象本身,这就是继承。
被__proto__引用的对象称作是原型(prototype),animal是rabbit的原型(译者注:这就是rabbit的__proto__属性引用了animal 的prototype属性)
(2)读时查找,不是写时
当读取一个对象时,比如this.prop,解释器会在它的原型中查找属性。
当设置一个属性值时,比如this.prop = value,那么就没有理由去查找了,这个属性(prop)会被直接添加到这个对象中(这里是this)。delete obj.prop也类似,它只删除对象本身的属性,原型中的属性保持原封不动。
(3)关于proto
如果你在阅读指南,这里我们叫的__proto__,在指南中表示为[[Prototype]]。双方括号是很重要的,因为有另一个属性叫做prototype。
2. Object.create, Object.getPrototypeOf
__proto__是一个非标准的属性,由Chrome/FireFox提供访问,在其他的浏览器中保持不可见。
所有的现代浏览器除了Opera(IE > 9)支持两个标准的函数来处理原型问题:
Object.ceate(prop[,props])
用给定了proto创建一个空对象:
var animal = { eats: true } rabbit = Object.create(animal) alert(rabbit.eats) // true
上面代码创建了一个空rabbit对象,并且原型设置为animal
rabbit对象创建好以后,我们可以往里添加属性了:
var animal = { eats: true } rabbit = Object.create(animal) rabbit.jumps = true
Object.creat函数的第二个参数props是可选的,它允许像新对象设置属性。这里就省略了,因为我们关系的继承。
(1)Object.getPrototypeOf(obj)
返回obj.__proto__的值。这个函数是标准的,可以在不能直接访问__proto__属性的浏览器中使用了。
var animal = { eats: true } rabbit = Object.create(animal) alert( Object.getPrototypeOf(rabbit) === animal ) // true
现代浏览器允许读取__proto__属性值,但是不能设置。
3. The prototype
有一些好的跨浏览器的方式设置__proto__属性,这将会使用构造器函数(constructor functions)。记住!任何函数创建一个对象都是通过new关键字的。
一个栗子:
function Rabbit(name) { this.name = name } var rabbit = new Rabbit('John') alert(rabbit.name) // John
new操作将原型的属性设置到rabbit对象的的__proto__属性中了。
让我们来看看它的原理,例如,new Rabbit 对象,而Rabbit是继承animal 的。
var animal = { eats: true } function Rabbit(name) { this.name = name } Rabbit.prototype = animal var rabbit = new Rabbit('John') alert( rabbit.eats ) // true, because rabbit.__proto__ == animal
Rabbit.prototype = animal 字面量意味着:对所有由new Rabbit创建的对象设__proto__ = animal
4. 跨浏览器 Object.create(proto)
Object.create(prop)函数功能的强大的,因为它允许从给定的对象直接继承。它可以由如下代码模拟:
function inherit(proto) { function F() {} F.prototype = proto return new F }
inherit(animal) 与Object.create(animal)是完全等同的,返回一个空的对象,并且object.__proto__ = animal。
一个栗子:
var animal = { eats: true } var rabbit = inherit(animal) alert(rabbit.eats) // true alert(rabbit.hasOwnProperty('eats')) // false, from prototype
来看一下它的原理是什么:
function inherit(proto) { function F() {} // (1) F.prototype = proto // (2) return new F() // (3) }