js中的继承与java中的继承不一样。一般通过两种方式完成:call()和apply()。js中的继承是以复制的形式完成的,复制一个父对象,而不像Java直接继承父对象,通过原型完成继承,也有缺点。总之,js中的继承只是对面向对象语言的一种形式上的模仿。本质上不是继承,但是效果是一样的。至于为什么需要继承:一般项目一般不需要,因为应用简单,但是需要用纯js做一些复杂的工具或者框架系统,比如webgis,或者js框架比如jquery,ext等,否则一个几千行代码的框架不一定要继承几万行,甚至无法维护。对象之间“继承”的五种方法。例如,现在有一个“动物”对象的构造函数。 functionAnimal(){ this.species="animal"; }还有一个“猫”对象的构造函数。 functionCat(name,color){ this.name=name; this.color=color; }“猫”如何继承“动物”?1.构造函数绑定第一种方法也是最简单的方法。使用call或apply方法将父对象的构造函数绑定到子对象上,即在子对象构造函数中添加一行: functionCat(name,color){ Animal.apply(this,arguments); this.name=name; this.color=color; } varcat1=newCat("大毛","黄色"); alert(cat1.species);//Animal2、原型模式第二种方法比较常见,使用prototype属性。如果“猫”的原型对象指向一个Animal实例,那么“猫”的所有实例都可以继承Animal。 Cat.prototype=newAnimal(); Cat.prototype.constructor=Cat; varcat1=newCat("大毛","黄"); alert(cat1.species);//在动物代码的第一行,我们将Cat的原型对象指向一个Animal实例。 Cat.prototype=newAnimal();相当于把原型对象原来的值完全删除,然后赋新值。但是第二行是什么意思? Cat.prototype.constructor=Cat;事实证明,任何原型对象都有一个构造函数属性,指向它的构造函数。如果没有“Cat.prototype=newAnimal();”行,Cat.prototype.constructor指向Cat;添加此行后,Cat.prototype.constructor指向Animal。 alert(Cat.prototype.constructor==Animal);//true更重要的是,每个实例还有一个constructor属性,默认称为原型对象的constructor属性。 alert(cat1.constructor==Cat.prototype.constructor);//true所以在运行“Cat.prototype=newAnimal();”这一行之后,cat1.constructor也指向了Animal! alert(cat1.constructor==Animal);//true这显然会造成继承链的混乱(cat1显然是用构造函数Cat生成的),所以我们必须手动修正,将Cat.prototype对象的构造函数值设置为ChangetoCat。这就是第二行的意思。这是非常重要的一点,在编程时必须遵循。下面遵循这一点,即如果替换了原型对象, o.prototype={};然后,下一步必须是将构造函数属性添加到新的原型对象,并将此属性指向原始构造函数。 o.prototype.constructor=o;3.原型直接继承第三种方法是对第二种方法的改进。因为在Animal对象中,不变的属性可以直接写到Animal.prototype中。因此,我们也可以让Cat()跳过Animal(),直接继承Animal.prototype。现在,我们首先重写Animal对象: functionAnimal(){} Animal.prototype.species="Animal";然后,将Cat的原型对象指向Animal的原型对象,这样继承就完成了。 Cat.prototype=Animal.prototype; Cat.prototype.constructor=Cat; varcat1=newCat("大毛","黄色"); alert(cat1.species);//Animal与前面的方法相比,这种方法的优点是效率更高(不需要执行和创建Animal的实例),并且节省内存。缺点是Cat.prototype和Animal.prototype现在指向同一个对象,所以对Cat.prototype的任何修改都会反映在Animal.prototype中。所以,上面这段代码其实是有问题的。请看第二行 Cat.prototype.constructor=Cat;这句话其实是改变了Animal.prototype对象的constructor属性! alert(Animal.prototype.constructor);//Cat4.使用空对象作为中介由于上述“直接继承原型”的缺点,还有第四种方法,使用空对象作为中介。 varF=function(){}; F.prototype=Animal.prototype; Cat.prototype=newF(); Cat.prototype.constructor=Cat;F是一个空对象,所以它几乎不占用内存。这时候修改Cat的原型对象不会影响到Animal的原型对象。 alert(Animal.prototype.constructor);//Animal,我们把上面的方法封装成一个函数,方便使用。 functionextend(Child,Parent){ varF=function(){}; F.prototype=Parent.prototype; Child.prototype=newF();.prototype.constructor=孩子; Child.uber=Parent.prototype; }使用时,方法如下 extend(Cat,Animal); varcat1=newCat("大毛","黄"); alert(cat1.species);//animals的extend函数就是YUI库实现继承的方式。另外,再说明一下,函数体的最后一行 Child.uber=Parent.prototype;意思是给子对象设置一个uber属性,直接指向父对象的prototype属性。(uber是德语单词,意思是“向上”、“上层”。)这相当于在子对象上开了一个通道,可以直接调用父对象的方法。这条线放在这里只是为了实现继承的完整性,纯属备用。五、复制继承上面是使用原型对象实现继承。我们也可以换一种思路,纯粹用“复制”的方式来实现继承。简单的说,如果把父对象的所有属性和方法都复制到子对象中,是不是就实现了继承呢?于是我们有了第五种方法。首先,将Animal的所有不变属性都放在它的原型对象上。 functionAnimal(){} Animal.prototype.species="Animal";然后,写一个函数来达到属性拷贝的目的。 functionextend2(Child,Parent){ varp=Parent.prototype; varc=Child.prototype; for(variinp){ c[i]=p[i]; } c.uber=p; }这个函数的作用是将父对象的原型对象中的属性一个一个复制到Child对象的原型对象中一。使用的时候这样写: extend2(Cat,Animal); varcat1=newCat("大毛","黄色"); alert(cat1.species);//动物一,什么是“非构造函数”继承?比如现在有一个对象叫“中文”。 varChinese={ nation:'中国' };还有一个对象叫做“医生”。 varDoctor={ career:'Doctor' }如何让“Doctor”继承“Chinese”,即如何生成一个“ChineseDoctor”的对象?这里要注意,这两个对象是普通对象,不是构造函数,不能用构造函数方法实现“继承”。2、object()方法json格式的发明者DouglasCrockford提出了一个object()函数可以做到这一点。 functionobject(o){ functionF(){} F.prototype=o; returnnewF(); }这个object()函数其实只做了一个首先是将子对象的prototype属性指向父对象,这样子对象和父对象就连接在一起了。使用时,第一步是在父对象的基础上生成一个子对象: varDoctor=object(Chinese);然后,添加子对象本身的属性: Doctor.career='Doctor';这时,子对象已经继承了父对象的属性。 alert(Doctor.nation);//中国3.除了使用“原型链”进行浅拷贝外,还有一种思路:将父对象的所有属性复制到子对象中,也可以实现继承。以下函数正在复制: functionextendCopy(p){ varc={}; for(variinp){ c[i]=p[i]; } c.uber=p; returnc; }使用的时候这样写: varDoctor=extendCopy(Chinese); Doctor.career='Doctor'; alert(Doctor.nation);//中国但是,这样的复制有一个问题。也就是说,如果父对象的属性等于一个数组或者另一个对象,那么实际上子对象得到的只是一个内存地址,并不是真正的拷贝,所以有可能父对象是篡改。请看,现在给中文添加一个“出生地”属性,它的值是一个数组。 Chinese.birthPlaces=['北京','上海','香港'];Doctor通过extendCopy()函数继承Chinese。 varDoctor=extendCopy(Chinese);然后,我们为医生的“出生地”添加一个城市: Doctor.birthPlaces.push('厦门');发生了什么?中国的“出生地”也改了! alert(Doctor.birthPlaces);//北京、上海、香港、厦门 alert(Chinese.birthPlaces);//北京、上海、香港、厦门所以extendCopy()只是复制基本类型的数据,我们称这种复制为“浅复制”。这就是jQuery早期实现继承的方式。4、深拷贝所谓“深拷贝”,就是可以实现真正意义上的数组和对象的拷贝。它的实现并不难,递归调用“浅拷贝”即可。 函数deepCopy(p,c){ varc=c||{}; for(variinp){ if(typeofp[i]==='object'){ c[i]=(p[i].constructor===大批)?[]:{}; deepCopy(p[i],c[i]); }else{ c[i]=p[i]; } } returnc; }使用的时候这样写: varDoctor=deepCopy(Chinese);现在,给父对象添加一个属性,值是一个数组。然后,在子对象上修改这个属性: Chinese.birthPlaces=['北京','上海','香港']; Doctor.birthPlaces.push('厦门');此时,父对象不会受到影响。 alert(Doctor.birthPlaces);//北京、上海、香港、厦门 alert(Chinese.birthPlaces);//北京、上海、香港目前jQuery库都是采用这种继承方式。
