JavaScript 继承机制解析

在JavaScript中,继承是一个复杂而又微妙的话题。尽管网络上有许多关于继承的讨论,但大多数解决方案都相当复杂。幸运的是,如果理解了以下三个关键词:__proto__、prototype和constructor,那么在现代浏览器中实现继承其实可以非常简单。

继承的核心机制:__proto__

在JavaScript中,每个对象都有一个隐藏的属性__proto__。当尝试访问一个在对象中未定义的属性时,JavaScript会自动在该对象的__proto__中查找该属性。例如:

var o = {}; o.toString();

在这个例子中,toString函数显然没有在o对象中声明。JavaScript会在o的__proto__中查找这个函数,并最终使用:

o.__proto__.toString()

这就是需要用来实现一个对象继承另一个对象的机制。

类不是对象

在JavaScript中,类和对象之间有一个真正的区别,人们往往容易忘记这一点。类不是对象。类描述对象,就像模板一样。对象是类的实例。

类模板:Prototype

JavaScript中,使用函数来创建类,这可能一开始有点奇怪。

var Mammal = function() { // 创建Mammal类 };

当使用函数作为类时,会使用另一个隐藏的属性:prototype。这是大多数误解发生的地方:prototype和__proto__不是同一回事!prototype描述了当对象被创建时(使用new机制)将继承哪些属性。

创建Cat类

让创建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: 首次发布

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485