正统的面相对象的语言都会提供extend之类的方法用于出来类的继承,但Javascript并不提供extend方法,在Javascript中使用继承需要用点技巧。
Javascript中的实例的属性和行为是由构造函数和原型两部分组成的,我们定义两个类:Person和zhangsan,它们在内存中的表现如下图1:
如果想让Zhangsan继承Person,那么我们需要把Person构造函数和原型中的属性和行为全部传给Zhangsan的构造函数和原型,如下图2所示:
Are you Ok?了解了继承的思路后,那么我们一步步完成Person和Zhangsan的继承功能。首先,我们需要定义Person类,如下代码:
[代码1]
// 定义Person类 function Person (name){ this.name = name; this.type = "人"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +",我的名字叫" + this.name); } } //定义Zhangsan类 function Zhangsan (name){ } Zhangsan.prototype={ }
Zhangsan虽然有自己特有的属性和行为,但它大部分属性和行为和Person相同,需要继承自Person类。如前所述,JavaScript中继承是要分别继承构造函数和原型中的属性和行为的。我们先让Zhangsan继承Person的构造函数中的行为和属性,如下代码:
[代码2]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } //定义Zhangsan类 function Zhangsan (name){ this.name = name; this.type = "黄"; } Zhangsan.prototype={ } //实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // 黄
运行正常,但我们怎么没看到继承的“味道”呢?我们在Zhangsan的构造函数中将Person的属性和行为复制了一份,与其说是继承不如说是“真巧,这两个类的构造函数除了函数名不同,其他地方都长得一样”。她的缺点很明显:如果Person类的构造函数有任何变动,我们也需要手动的同步修改Zhangsan类的构造函数,同样一份代码,我们复制了一份写在了程序中 的不同地方,这违法了DRY原则,降低了代码的可维护性。
好了,让我们来改进它:
[代码3]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person(name); } Zhangsan.prototype={ } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // undefined
我们在Zhangsan的构造函数里调用Person()函数,希望它内部的ths.xxx可以在Zhangsan类的构造函数里执行一遍,但奇怪的是,出现“console.info(zs.type);”时,输出的是undefined,这是怎么回事呢?
这和Person的调用方式有关。在JavaScript中,function有两种不同的调用方法:
作为函数存在,直接用“()”调用,例如“function test(){}; test();”test被用作函数,直接被“()”符号调用。
作为类的构造函数存在,使用new调用,例如“function test(){}; new test();”test作为类的构造函数,通过new进行test类的实例化。这两种方法的调用,function内部的this指向会有所不同---作为函数的function,其this指向的是window,而作为构造函数的function,其this指向的实例对象。
上面代码中,Zhangsan类构造函数中的Person是通过函数方式调用的,它内部的this指向的是window对象,起效果等同于如下代码:
[代码4]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ window.name = name; window.type = "黄"; } Zhangsan.prototype={ } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // undefined console.info(type); // 黄 (window.type可以省略写成type)
如果想达到[代码3]的效果,让Person内部this指向Zhangsan类的实例,可以通过call或apply方法实现,如下:
[代码5]
// 定义Person类 function Person (name){ this.name = name; this.type = "黄"; } Person.prototype={ say : function(){ console.info("我是一个"+ this.type +"种人,我的名字叫" + this.name); } } // 定义Zhangsan类 function Zhangsan (name){ Person.call(this,name); } Zhangsan.prototype={ } // 实例化Zhangsan对象 var zs = new Zhangsan("张三"); console.info(zs.type); // 黄