在JavaScript中,继承是一个复杂而又微妙的话题。尽管网络上有许多关于继承的讨论,但大多数解决方案都相当复杂。幸运的是,如果理解了以下三个关键词:__proto__、prototype和constructor,那么在现代浏览器中实现继承其实可以非常简单。
在JavaScript中,每个对象都有一个隐藏的属性__proto__。当尝试访问一个在对象中未定义的属性时,JavaScript会自动在该对象的__proto__中查找该属性。例如:
var o = {};
o.toString();
在这个例子中,toString函数显然没有在o对象中声明。JavaScript会在o的__proto__中查找这个函数,并最终使用:
o.__proto__.toString()
这就是需要用来实现一个对象继承另一个对象的机制。
在JavaScript中,类和对象之间有一个真正的区别,人们往往容易忘记这一点。类不是对象。类描述对象,就像模板一样。对象是类的实例。
在JavaScript中,使用函数来创建类,这可能一开始有点奇怪。
var Mammal = function() {
// 创建Mammal类
};
当使用函数作为类时,会使用另一个隐藏的属性:prototype。这是大多数误解发生的地方:prototype和__proto__不是同一回事!prototype描述了当对象被创建时(使用new机制)将继承哪些属性。
让创建Cat类。
var Cat = function() {
// 创建Cat类
};
现在,为了继承:
Cat.prototype.__proto__ = Mammal.prototype;
就是这样!现在当创建一个新的Cat时,它的__proto__将是Cat.prototype,它的__proto__.__proto__将是Mammal.prototype,所以新出生的小猫将能够访问Cat和Mammal的属性。
向类添加静态属性真的很简单。只需将属性设置在类本身,而不是设置在prototype中:
Mammal.legs = 4; // 所有哺乳动物都有4条腿
问题是Cat无法访问legs属性。所以需要另一种继承:
Cat.__proto__ = Mammal;
再一次,不需要添加任何更多的东西。
在解释new在JavaScript中的作用时并不完全正确。忘记了一行:
var m = {};
m.__proto__ = Mammal.prototype;
m.constructor = Mammal;
m.constructor.call(m);
这意味着隐藏的属性constructor持有类。这对于访问静态属性非常有用。
m.constructor.legs; // 4! 哺乳动物总是有4条腿。
当创建一个新的Cat时,必须显式地调用父构造函数。这可以通过__proto__和constructor实现:
var c = new Cat();
c.__proto__; // Cat.prototype
c.__proto__.__proto__; // Mammal.prototype
c.__proto__.__proto__.constructor; // Mammal
c.__proto__.__proto__.constructor.call(c); // 调用Mammal构造函数
为了简化继承过程,这里有一个非常有帮助但简单的函数:
Function.prototype.subClass = function(superClass) {
this.prototype.__proto__ = superClass.prototype;
this.__proto__ = superClass;
this.prototype.superClass = superClass;
};
前两行提供了属性和静态属性的继承。superClass属性是超类/父类的快捷方式 - 这对于调用超类/父类的函数(如构造函数)非常有用。
这是一个增强版本,极大地简化了JavaScript中类创建过程。
Function.prototype.toClass = function(superClass, members) {
if (members == undefined) {
members = superClass;
superClass = null;
}
if (superClass) {
this.prototype.__proto__ = superClass.constructor == Object ? superClass : superClass.prototype;
this.__proto__ = superClass;
this.prototype.superClass = superClass;
this.prototype.static = this;
}
for (var k in members) {
this.prototype[k] = members[k];
}
return this;
};
使用这个函数时,它创建了一个静态属性,它是constructor的确切同义词,但它提高了访问静态属性时的可读性。
Animal = function() {}
Animal.type = "Animal";
Animal.ShowType = function() {
console.log(this.type);
}
Animal.toClass({
talk: function() {
console.log(this.sound + "!");
}
});
Dog = function() {
this.superClass();
}
Dog.type = "Dog";
Dog.toClass(Animal, {
sound: "bark"
});
Cat = function() {
this.superClass();
}
Cat.toClass(Animal, {
sound: "meow"
});
a = new Animal();
a.talk(); // "undefined!"
d = new Dog();
d.talk(); // "bark!"
c = new Cat();
c.talk(); // "meow!"
Animal.ShowType(); // "Animal"
Dog.ShowType(); // "Dog"
c.static.type; // "Cat"
由于__proto__在IE中没有暴露,这在Microsoft浏览器中根本不起作用。
还会在Safari < 5和Opera < 10.50中遇到类似的问题。
2012-09-29: 首次发布