# 继承的几种方法 继承是面向对象语言中的一个最为人津津乐道的概念。许多面向对象都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是**依靠原型链**来实现的。 > 继承的概念:子类继承父类中的属性和方法; > 继承的目的:子类直接使用父类的方法,不需要再重写一遍; > 继承的实际应用:B想有一个getX的方法,我们发现A中已经有了,我接下来只需要让B作为儿子,让A作为父亲,让B继承A中的属性和方法; - 知识点一:原型继承 - 知识点二:call继承 - 知识点三:call加原型继承(推荐) - 知识点四:冒充对象继承 - 知识点五:中间类继承 # 知识点一、原型继承 原型继承的原理:B要继承A,只需要让B的原型等于A的一个实例即可 ->B.prototype=new A; function A() {   this.x = 100; } A.prototype.getX = function () {   console.log(this.x); }; function B() { this.test="test" } B.prototype.getY = function () {   console.log("B.prototype"); }; B.prototype = new A; var b = new B; console.dir(b); b.x=200; b.getX();//200; A.prototype.getX();//undefined //子类重写父类的方法 b.__proto__.__proto__.getX = function () { console.log(this.x + 1000); }; b.getX();//1200; 1)原型继承是我们JS中最常用的一种继承方式,属于改写原生的原型,指向需要继承的实例;; 2)原型继承不是把父类的属性和方法复制一份给自己,而是更改了原型链的查找顺序,我们b想要使用getX这个方法,是通过__proto__一级级的向上查找,最后也找到A的原型上,获取getX并且使用 -> b.__proto__.__proto__.getX=A.prototype.getX 3)子类不仅仅可以获取使用父类上的方法,而且还可以把父类原型上的方法进行修改 b.__proto__.__proto__.getX=function(){console.log(this.x+1000);} 就是把父类A原型上的方法修改了,这样的话,A的其他的实例也会受到影响 ->"这叫做类的多态(是多态中的重写):子类重写父类的方法" 4)在原型继承中,子类可以把父类中私有和公有的属性/方法都继承过来使用;并且都是作为子类B公有的属性/方法;(因为B每次查找都经过A的实例,所以A中的私有方法和属性,会被B所继承) ![](http://i.imgur.com/eQKCfNi.png) ##### 原型继承的应用; > 写一个方法,求评委打分的平均分(去掉一个最高分,去掉一个最低分) function fn() { arguments.__proto__ = Array.prototype; arguments.sort(function (a, b) { return a - b; }); arguments.shift();//去掉最高分 arguments.pop();//去掉最低分 console.log(eval(arguments.join("+")) / arguments.length); console.log(arguments.join("+") , arguments.length); } fn(56,67,78,89,90,12,23,34 ,45); fn(56, 67, 78, 89, 90, 12, 23, 34, 45 , 45,14,34,67); ##### 原型继承的缺点: function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){} //继承了 SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); console.log(instance2.colors); //"red,blue,green,black" > 1、完全继承了A,自己原型上的方法被吃掉了;杀敌1000,自损800的做法; > 2、最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。(所有属性都是大家伙的了) > 3、由第二个问题导致的就是,在创建子类的实例时,不能向父类的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。 所以引出了call继承(apply同样的原理); # 知识点二、call继承(apply同样的原理,借用构造函数/伪造对象/经典继承) call继承的原理:在子类B的私有属性中,把父类A当做一个普通的函数执行,并且把里面的this变为子类B的一个实例b,这样的话,在执行A的时候,A中写的this.xxx=xxx都相当于给b增加的私有的属性 function A() { this.x = 100;//b.x=100 给b增加一个私有的属性x=100 this.flagA="flagA" } A.prototype.getX = function () { console.log(this.x); }; function B() { //this->b //A() ->作为一个普通的函数执行,this->window,A的原型是起不到作用的 A.call(this);//->执行A函数,把A里面的this变为b this.flagB="flagB" } B.prototype.getBPrototype = function () { console.log("B.prototype"); }; var b = new B; console.log(b); 1)call继承首先只能继承父类A中的私有的属性(this.xxx=xxx),并且和原型不一样,call继承是把A中的私有的属性复制一份直接的给了B的实例 2)把父类A中的私有属性继承到子类B中的私有的属性 b的结构如下; ![](http://i.imgur.com/5V9q6WS.png) ##### call继承的优点 对实例1的操作不会影响实例2, function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ // 继承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); console.log(instance2.colors); //"red,blue,green" ##### call继承的缺点 > 只继承了A的私有属性,没有继承A的原型; > 如果仅仅是call继承,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在父类的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。 所以引出了call继承+原型继承; # 知识点三、call继承加原型继承(也叫做伪经典继承) **call继承+原型继承** function A() { this.x = 100; this.flagA="flagA" } A.prototype.getX = function () { console.log(this.x); }; function B() { A.call(this); this.flagB="flagB" } B.prototype.getBPrototype = function () { console.log("B.prototype"); }; B.prototype = new A; var b = new B; console.log(b); ![](http://i.imgur.com/KXddTTS.png) ##### call继承加原型继承解决的事情 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且, instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。 function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ console.log(this.name); }; function SubType(name, age){ //继承属性 SuperType.call(this, name); this.age = age; } //继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType;//constructor 需要写回去,否则SubType虽然继承了SuperType的私有属性和公有属性,但是SubType原来的原型被重写了; SubType.prototype.sayAge = function(){ console.log(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); console.log(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27 > 用这种模式一定要注意原型的constructor重写会子类,否则SubType虽然继承了SuperType的私有属性和公有属性,但是SubType原来的原型被重写了; # 知识点四、冒充对象继承(call继承的升级版) 冒充对象继承:在子类B的私有函数体中,创建一个父类A的实例temp(temp中就存有父类A中的私有和公有的属性/方法),我们把temp当做一个普通的对象进行遍历,分别把temp中存有的属性都存储到我们子类B的私有中一份 function A() { this.x = 100; this.flagA="flagA" } A.prototype.getX = function () { console.log(this.x); }; function B() { // A.call(this); this.flagB="flagB" var temp = new A;//->temp中现在就存有了A中的私有和公有的属性/方法   for (var key in temp) {    this[key] = temp[key];   }   temp = null; } B.prototype.getBPrototype = function () { console.log("B.prototype"); }; var b = new B; console.log(b); - 1)、冒充对象继承首先继承父类中的私有和公有的属性/方法,并且和原型不一样,冒充对象继承是把A中的私有的属性和共公有的方法复制一份直接的给了B的实例 - 2)、把父类A中的私有属性和共公有的方法继承到子类B中的私有的属性 > 备注:temp只是一个借用的对象,所以用完记得置为null,类似数组排序中的借用的那个对象,仅仅只是内部处理所需的,没有实际的意义; ![](http://i.imgur.com/3qIm86o.png) # test