在JavaScript中实现继承
作者:土豆爸爸
JavaScript脚本语言是一种功能强大的面向对象的语言。本文描述了如何在JavaScript中实现继承。Prototype
JavaScript没有实现类继承,但可以通过prototype实现。每个JavaScript类中都包含一个prototype对象,当访问一个对象的属性和方法时,它首先从当前对象查找,如果该属性在JavaScript类定义,则直接访问;否则,从类的prototype对象中查找,如果找到则直接访问,否则从prototype的prototype对象中查找,以此类推,直接prototype对象为空。换句话说,当前对象继承了它所在类的prototype的所有属性和方法。
但是,prototype对象是属于一个类的,而一个类又可以创建多个对象,因而,访问继承来的属性时读取和写入是有区别的。当读取一个继承的属性时,按上面所述的步骤查找属性;当写入一个属性时,则从prototype中复制数据到当前对象;再次读取时,访问的是当前对象的属性。对于方法不存在这种问题。
实现继承
JavaScript类的prototype缺省类型是Object类,所以可以说,JavaScript中所有类都从Object类继承。我们可以修改一个类的prototype属性,使它指向一个其它对象,从而实现继承。实现继承通常有两种方式:
- 修改一个类的prototype属性,使其指向一个父类对象
- 修改一个类的prototype对象的constructor属性,为其指定一个父类的构造函数
两种实现方法基本相同,但存在一些差别。代码清单1采用第一种方式,代码清单2采用第二种方式。
//定义基类Shape function Shape(x, y) { this.x = x; this.y = y; this.fromOrigin = function() { //定义方法 return Math.sqrt(this.x * this.x + this.y * this.y); } } //可以采用这种方式 /* Shape.prototype.fromOrigin = function() { return Math.sqrt(this.x * this.x + this.y * this.y); } */ //定义子类Circle function Circle(x, y, r) { Circle.prototype.constructor(x, y); //调用父类构造函数 this.r = r; } Circle.prototype = new Shape(); //绑定父对象 //测试 var c = new Circle(5, 5, 10); print(c); c.x = 3; //修改属性 c.y = 4; print(c); //打印 function print(c) { document.write("Circle.prototype.x=" + Circle.prototype.x, "<br/>"); document.write("Circle.prototype.y=" + Circle.prototype.y, "<br/>"); document.write("c.x=" + c.x, "<br/>"); document.write("c.y=" + c.y, "<br/>"); document.write("c.r=" + c.r, "<br/>"); document.write("c.fromOrigin()="+ c.fromOrigin(), "<br/>"); } 清单1:修改一个类的prototype属性
输出结果;
Circle.prototype.x=5 Circle.prototype.y=5 c.x=5 c.y=5 c.r=10 c.fromOrigin()=7.0710678118654755 Circle.prototype.x=5 Circle.prototype.y=5 c.x=3 c.y=4 c.r=10 c.fromOrigin()=5
上面的代码中,通过Circle.prototype = new Shape(); 绑定父对象,Circle对象包含了一个Shape所有的属性和方法。
第一段输出时,我们创建了一个Circle对象,在Circle的构造函数中调用父类Shape的构造函数,从而初始化了Shape中的x、y。当访问c.x时,实际访问的Circle.prototype引用的Shape对象的x。
第二段输出时,我们修改了Circle对象的x和y,此时将Circle.prototype引用的Shape对象的x和y复制到Circle对象中,对Circle对象的修改不影响Circle.prototype引用的Shape对象。
fromOrigin方法既可以在Shape的构造函数中指定,也可以在Shape.prototype中指定。
//定义基类Shape function Shape(x, y) { this.x = x; this.y = y; this.fromOrigin = function() { //定义方法 return Math.sqrt(this.x * this.x + this.y * this.y); } } //不能采用这种方式 /* Shape.prototype.fromOrigin = function() { return Math.sqrt(this.x * this.x + this.y * this.y); } */ //定义子类Circle function Circle(x, y, r) { Circle.prototype.constructor(x, y); //调用父类构造函数 this.r = r; } Circle.prototype.constructor = Shape; //绑定prototype的构造函数为Shape(x, y) //测试 var c = new Circle(5, 5, 10); print(c); c.x = 3; //修改属性 c.y = 4; print(c); //打印 function print(c) { document.write("Circle.prototype.x=" + Circle.prototype.x, "<br/>"); document.write("Circle.prototype.y=" + Circle.prototype.y, "<br/>"); document.write("c.x=" + c.x, "<br/>"); document.write("c.y=" + c.y, "<br/>"); document.write("c.r=" + c.r, "<br/>"); document.write("c.fromOrigin()="+ c.fromOrigin(), "<br/>"); //document.write(Circle.prototype instanceof Object); } 清单2:修改一个类的prototype对象的constructor属性
清单2的输出结果与清单1的输出结果相同。
上面的代码中,通过Circle.prototype.constructor = Shape; 绑定prototype的构造函数为Shape(x, y)。
fromOrigin方法只能在Shape的构造函数中指定,不可以在Shape.prototype中指定。原因是Circle.prototype.constructor = Shape;只指定的Circle.prototype的构造函数,Circle的prototype对象的类型没有改变,仍然是Object类型,所以在Shape.prototype绑定的方法对Circle不可见。
结论
JavaScript是一种面向对象的语言,虽然表面上看起来不是很明显(其中的顶级函数属于Global对象)。我们可以通过JavaScript类的prototype来实现继承,文中提到两种方法以及它们的细微差别。
参考资料
David Flanagan, JavaScript: The Definitive Guide, 4th Edition, O'Reilly, 2001。