原型(prototype)是JavaScript实现面向对象编程的一个基础,但它并不是唯一的构造类的方法,我们完全可以不使用prototype而实现类的编写(把属性和方法的附加全都写在构造函数里面就行了)。不过原型除了可以为Object的子类添加新的属性和方法外,它还可以为脚本环境中的内部对象继续添加原型属性和方法,比如我们最常用的给内部对象String添加一个Trim方法来删除字符串两端的空格,代码为:
{
return this .replace( / ( ^ /s * ) | (/s * $) / g, '');
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
}
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
这样我们就可以在任何的String实例中使用Trim方法了,用惯了这种原型系统,有的时候反而还觉得传统OOP没有它爽,kaka。言归正传,继续讲我们的原型继承法。
原型继承法的原理:
原型继承法的关键代码是其构造函数function ArrayList02()下的第一句:
ArrayList02.prototype.constructor = ArrayList02;
Ae… 把prototype都覆盖成基类的一个实例了,子类还怎么使用prototype呢?这里不要着急,反正JavaScript的对象实例都是可以动态增删属性和方法的,基类实例作为prototype不就正好等于extends了CollectionBase吗?之后再使用XXX.prototype.xxx = function(),可以继续获得新增了属性和方法的对象。注意ArrayList02.prototype是在ArrayList02的构造函数外被初始化为基类的实例的。
再来看第二句,为什么要把ArrayList02赋值给新prototype的constructor呢?如果不做这个赋值,当我们从ArrayList02的实例中,通过???.prototype.constructor去却其构造函数,将会获得CollectionBase。这不是我们的本意,而且这是使用instanceof关键之比较对象实例和对象也会出错。
原型继承法的缺陷:
原型继承法有两个缺陷,第一个是由于类的原型(prototype)实际上是一个Object的实例,它不能再次被实例化(它的初始化在脚本装载时已经执行完毕)。这么意思呢?我们知道在新建对象实例时,我们使用语句new ArrayList02(),这时我们可以看做JavaScript脚本引擎把prototype的一个浅拷贝作为this返回给类的实例(这里其实没有发生拷贝,只是利用浅拷贝这个概念来帮助理解),如果类没有附加任何原型属性和原型方法,那么就等于返回了一个new Object()实例。问题就出在这里了,由于new对prototype执行的是浅拷贝,如果prototype的原型属性里有对象类型的属性,那么就会造成共享对象实例的问题(类似于在传统OOP的类定义中使用了static修饰符来修饰了属性)。这个缺陷下面会有示例演示,避免的办法就是不要在基类中定义对象类型的属性,比如: Array、Object和Date等。
第二个缺陷和上次讲的"构造继承法"的缺陷差不多,也是关于子类定义是语句顺序的。就是说代码:ArrayList02.prototype = new CollectionBase();必须在所有prototype定义之前执行,很简单,如果在原型属性和方法都导入完成后,再执行这个语句,就定于把之前的导入全都覆盖掉了:(。解决办法就是按我给文章(1)中的那个顺序来写,郁闷吧?kaka
原型继承法的示例:
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0be121fa5b8988fbabbbc526af3b0fc0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/b854634c0904529d4018c4c3336be836.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/4fd96b3cf02f4c7b5c8964ac8167f7af.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
示例运行结果为:
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
发现问题了吧?实例arrayList22的foo()居然输出了a,b,c,a,b@_@... 这就是前面说的prototype对象浅拷贝带来的问题。不过为什么arrayList22中的集合计数器却仍然是3呢?这是因为this.m_Count是整数类型,这种类型又叫值类型(和C#里的值类型、对象类型概念一样的)。值类型不存在浅拷贝和深拷贝的问题,可以看成都是深拷贝。
小结:JavaScript的原型继承法,虽然有prototype浅拷贝那么严重的bug,不过它却是使用比较多的继承方式。因为我们很少在基类里定义属性,更别说对象类型的属性了,所以引发这个错误的可能性不是很大,只是它是个潜在的隐患:(。至于第二个缺陷,也是一个潜在bug,只要自己定义类的时候能比较清醒就不会犯错误。'重载'也比较简单,如果在ArrayList02.prototype = new CollectionBase();后有重名的属性或方法导入,自动覆盖基类中的属性或方法就像当于重载了。
应用场景:基类没有属性,至少是要没有对象类型的属性。这种继承的优点是保持了子类构造函数的完整,可以不在里面添加任何和继承有关系的代码,所有继承和重载操作都由对原型(prototype)的操作来完成。
未完