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采用第二种方式。
//定义基类Shapefunction 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);}*///定义子类Circlefunction 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=5Circle.prototype.y=5c.x=5c.y=5c.r=10c.fromOrigin()=7.0710678118654755Circle.prototype.x=5Circle.prototype.y=5c.x=3c.y=4c.r=10c.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中指定。
//定义基类Shapefunction 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);}*///定义子类Circlefunction 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。