这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要介绍了 js 类式继承的具体实现方法,有需要的朋友可以参考一下
在开始摆弄代码之前,应该搞清楚使用继承的目的和能带来什么好处。一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化类之间的耦合。而要做到这两者都兼顾是很难的,我们需要根据具体的条件和环境下决定我们应该采取什么方法。根据我们对面向对象语言中继承的了解,继承会带类直接的强耦合,但 js 由于其特有的灵活性,可以设计出强耦合和弱耦合,高效率和低效率的代码。而具体用什么,看情况。
下面提供 js 实现继承的三种方法:类式继承,原型继承,掺元类。这里先简述类式继承,后两种在往后的随便中简述,请多多关注、指导,谢谢。
类式继承。
js 类式继承的实现依靠原型链来实现的。什么是原型链?js 中对象有个属性 prototy,这个属性返回对象类型的引用,用于提供对象的类的一组基本功能。
貌似对 prototype 有印象,对了,我们经常这样用代码。
- var Person = function(){
- this.name = "liyatang";
- };
- Person.prototype = {
- //可以在这里提供Person的基本功能
- getName : function(){
- return this.name;
- }
- }
我们把类的基本功能放在 prototype 属性里,表示 Person 这个对象的引用有 XXX 功能。
在理解原型后,需要理解下什么是原型链。在访问对象的某个成员 (属性或方法) 时,如果这个成员未见于当前对象,那么 js 会在 prototype 属性所指的那个对象中查找它,如果还没有找到,就继续到下一级的 prototype 所指的对象中查找,直至找到。如果没有找到就会返回 undifined。
那么原型链给我们什么提示呢?很容易联想到,原型链意味着让一个类继承另一个类,只需将子类的 prototype 设置为指向父类的一个实例即可。这就把父类的成员绑定到子类上了,因为在子类上查找不到某个成员时会往父类中查找。(以上这两段用词不严谨,只在用通俗易懂的言语描述)
下面我们需要个 Chinese 类,需要继承 Person 类的 name 和 getName 成员。
- var Chinese = function(name, nation){
- //继承,需要调用父类的构造函数,可以用call调用,this指向Chinese
- //使Person在此作用域上,才可以调用Person的成员
- Person.call(this,name);
- this.nation = nation;
- };
- Chinese.prototype = Person.prototype;
- //这里不可和以前一样,因为覆盖掉了prototype属性
- //Chinese.prototype = {
- // getNation : function(){
- // return this.nation;
- // }
- //};
- //以后的方法都需要这样加
- Chinese.prototype.getNation = function(){
- return this.nation;
- };
继承关系就建立了,我们这样调用它
- var c = new Chinese("liyatang","China");
- alert(c.getName());// liyatang
于是类式继承就这样完成了。难道真的完成了嘛,用 firebug 在 alert 那里设断点,会发现原来的 Person.prototype 被修改了,添加了 getNation 方法。
这是因为在上面的代码 Chinese.prototype = Person.prototype; 这是引用类型,修改 Chinese 同时也修改了 Person。这本身就是不能容忍的,且使类之间形成强耦合性,这不是我们要的效果。
我们可以另起一个对象或实例化一个实例来弱化耦合性。
- //第一种
- //Chinese.prototype = new Person();
- //第二种
- //var F = function(){};
- //F.prototype = Person.prototype;
- //Chinese.prototype = F.prototype;
这两种方法有什么区别呢。在第二种中添加了一个空函数 F,这样做可以避免创建父类的一个实例,因为有可能父类会比较庞大,而且父类的构造函数会有一些副作用,或者说会执行大量的计算任务。所以力荐第二种方法。
到此,完了嘛,还没有!在对象的属性 prototype 下面有个属性 constructor,它保存了对构造特定对象实例的函数的引用。根据这个说法 Chiese.prototype.constructor 应该等于 Chinese,实际上不是。
回忆之前在设置 Chiese 的原型链时,我们把 Person.prototype 覆盖掉了 Chiese.prototype。所以此时的 Chiese.prototype.constructor 是 Person。我们还需要添加以下代码
- //对这里的if条件不需要细究,知道Chinese.prototype.constructor = Chinese就行
- if(Chinese.prototype.constructor == Object.prototype.constructor){
- Chinese.prototype.constructor = Chinese;
- }
整理全部代码如下
- var Person = function(name){
- this.name = name;
- };
- Person.prototype = {
- getName : function(){
- return this.name;
- }
- };
- var Chinese = function(name, nation){
- Person.call(this,name);
- this.nation = nation;
- };
- var F = function(){};
- F.prototype = Person.prototype;
- Chinese.prototype = F.prototype;
- if(Chinese.prototype.constructor == Object.prototype.constructor){
- Chinese.prototype.constructor = Chinese;
- }
- Chinese.prototype.getNation = function(){
- return this.nation;
- };
- var c = new Chinese("liyatang","China");
- alert(c.getName());
如果可以把继承的代码放在一个函数里,方便代码复用,最后整理代码如下
- function extend(subClass,superClass){
- var F = function(){};
- F.prototype = superClass.prototype;
- subClass.prototype = new F();
- subClass.prototype.constructor = subClass;
- subClass.superclass = superClass.prototype; //加多了个属性指向父类本身以便调用父类函数
- if(superClass.prototype.constructor == Object.prototype.constructor){
- superClass.prototype.constructor = superClass;
- }
- }
- var Person = function(name){
- this.name = name;
- };
- Person.prototype = {
- getName : function(){
- return this.name;
- }
- };
- var Chinese = function(name, nation){
- Person.call(this,name);
- this.nation = nation;
- };
- extend(Chinese, Person);
- Chinese.prototype.getNation = function(){
- return this.nation;
- };
- var c = new Chinese("liyatang","China");
- alert(c.getName());
发表后修改:
在一楼的评论下,我对那个 extend 函数又有新的看法。之前在讨论如何设置原型链时提出了两种方法
- //第一种
- //Chinese.prototype = new Person();
- //第二种
- //var F = function(){};
- //F.prototype = Person.prototype;
- //Chinese.prototype = F.prototype;
虽然第二种减少了调用父类的构造函数这条路,但在设计 Chinese 类时用了 Person.call(this,name); 这里也相当于调用了父类的构造函数。
然而用第一种方法的话可以减少在 Chinese 中再写 Person.call(this,name);,这部分代码在子类中往往会遗忘。不妨把这种功能代码放在了 extend 里。就只写
Chinese.prototype = new Person(); 也达到同样的目的:耦合不强。
但遗忘的一点是,Chinese.prototype = new Person(); 这样写对嘛。答案是不对!很明显 new Person() 需要传一个 name 参数的。我们不可能在 extend 函数里做这部分工作,只好在 Chinese 类里调用父类的构造函数了。这样也符合面向对象的思路。
所以,还是力荐用第二种方法。
第一次这样写有关技术类的文章,基本是按自己的思路铺展开来,难免会有一些没有考虑到的地方和解释的不清楚的地方,望留言反馈,谢谢。
来源: http://www.phperz.com/article/17/0626/277577.html