面向对象的程序设计-继承
原型链
就是让一个原型等于另一个类的实例,以此类推
1 function SuperType(){ 2 this.property = true; 3 } 4 5 SuperType.prototype.getSuperValue = function(){ 6 return this.property; 7 }; 8 9 function SuberType(){ 10 this.property = false; 11 } 12 13 SuberType.prototype = new SuperType(); 14 15 SuberType.prototype.getSuberValue = function(){ 16 return this.property; 17 } 18 19 var instance = new SuberType(); 20 console.log(instance.getSuperValue()); //true
要注意instance.constructor现在指向的是SuperType,这是因为原来SubType.prototype中的constructor被重写了
1、别忘记默认的原型
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部函数指针,指向Object.prototype。
SubType继承了SuperType,而SuperType继承了Object。当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法
2、确定原型和实例的关系
第一种方式使用instanceof操作符,只要用这个操作符测试实例与原型中出现过得构造函数,就会返回true
1 console.log(instance instanceof Object); //true 2 console.log(instance instanceof SuperType); //true
第二种方式是使用isPrototypeOf()方法
1 console.log(Object.prototype.isPrototypeOf(instance)); //true 2 console.log(SuperType.prototype.isPrototypeOf(instance)); //true
3、谨慎的定义方法
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后
1 function SuperType(){ 2 this.property = true; 3 } 4 5 SuperType.prototype.getSuperValue = function(){ 6 return this.property; 7 }; 8 9 function SuberType(){ 10 this.property = false; 11 } 12 13 SuberType.prototype = new SuperType(); 14 15 SuberType.prototype.getSuberValue = function(){ 16 return this.property; 17 } 18 SuberType.prototype.getSuperValue = function(){ 19 return false; 20 } 21 22 var instance = new SuberType(); 23 console.log(instance.getSuperValue()); //false
还有一点需要注意,即在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样就会重写原型链
1 function SuperType(){ 2 this.property = true; 3 } 4 5 SuperType.prototype.getSuperValue = function(){ 6 return this.property; 7 }; 8 9 function SuberType(){ 10 this.property = false; 11 } 12 13 SuberType.prototype = new SuperType(); 14 15 SuberType.prototype = { 16 getSuberValue : function(){ 17 return this.subproperty; 18 }, 19 someOtherMethod : function(){ 20 return false; 21 } 22 }; 23 24 var instance = new SuberType(); 25 console.log(instance.getSuperValue()); //error
4、原型链的问题
虽然原型链很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引用类型值的原型,前面介绍过包含引用类型值的原型属性会被所有的实例共享
1 function SuperType(){ 2 this.colors = ["red","blue","green"]; 3 } 4 5 function SuberType(){ 6 7 } 8 9 SuberType.prototype = new SuperType(); 10 11 var instance1 = new SuberType(); 12 instance1.colors.push("black"); 13 console.log(instance1.colors); //["red", "blue", "green", "black"] 14 15 var instance2 = new SuberType(); 16 console.log(instance2.colors); //["red", "blue", "green", "black"]
原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数
借用构造函数
这种技术的思想相当简单,就是在子类型构造函数内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数
1 function SuperType(){ 2 this.colors = ["red","blue","green"]; 3 } 4 function SuberType(){ 5 SuperType.call(this); 6 } 7 var instance1 = new SuberType(); 8 instance1.colors.push("black"); 9 console.log(instance1.colors); //["red", "blue", "green", "black"] 10 11 var instance2 = new SuberType(); 12 console.log(instance2.colors); //["red", "blue", "green"]
我们实际上在新创建的SubType实例的环境下调用了SuperType构造函数
1、传递参数
1 function SuperType(name){ 2 this.name = name; 3 } 4 5 function SuberType(){ 6 //继承了SuperType,同时还传递了参数 7 SuperType.call(this,"Nicholas"); 8 this.age = 29; 9 } 10 11 var instance = new SuberType(); 12 console.log(instance.name); //Nicholas 13 console.log(instance.age); //29
2、借用构造函数的问题
方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式
组合继承
组合有时候也叫伪经典继承,值得是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式,其思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
1 function SuperType(name){ 2 this.name = name; 3 this.colors = ["red","blue","green"]; 4 } 5 6 SuperType.prototype.sayName = function(){ 7 console.log(this.name); 8 }; 9 10 function SubperType(name,age){ 11 SuperType.call(this,name); 12 this.age =age; 13 } 14 15 SubperType.prototype = new SuperType(); 16 SubperType.prototype.constructor = SubperType; 17 SubperType.prototype.sayAge = function(){ 18 console.log(this.age); 19 }; 20 21 var instance1 = new SubperType("Nicholas",29); 22 instance1.colors.push("black"); 23 console.log(instance1.colors); //["red", "blue", "green", "black"] 24 instance1.sayName(); //Nicholas 25 instance1.sayAge(); //29 26 27 var instance2 = new SubperType("Greg",27); 28 console.log(instance2.colors); //["red", "blue", "green"] 29 instance2.sayName(); //Greg 30 instance2.sayAge(); //27
组合继承成为JavaScript中最常用的继承模式
原型式继承
道格拉斯.克罗克福德介绍了这一种继承的方法,思路是借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型为了达到这个目的,他给出了以下函数
1 function object(o){ 2 function F(){} 3 F.prototype = o; 4 return new F(); 5 } 6 7 var person = { 8 name: "Nicholas", 9 friends: ["Shelby","Court","Van"] 10 }; 11 12 var anotherPerson = object(person); 13 anotherPerson.name = "Greg"; 14 anotherPerson.friends.push("Rob"); 15 16 var yetAnotherPerson = object(person); 17 yetAnotherPerson.name = "Linda"; 18 yetAnotherPerson.friends.push("Barbie"); 19 20 console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]
这以为着person.friends不仅属于person所有,而且也会被anotherPerson以及yetAnotherPerson共享ECMAScript5通过新增Object.create()方法规范化了原型式继承,这个方法接受两个参数。在传入一个参数的情况下与object()方法的行为相同
1 var person = { 2 name: "Nicholas", 3 friends: ["Shelby","Court","Van"] 4 }; 5 6 var anotherPerson = Object.create(person); 7 anotherPerson.name = "Greg"; 8 anotherPerson.friends.push("Rob"); 9 10 var yetAnotherPerson = Object.create(person); 11 yetAnotherPerson.name = "Linda"; 12 yetAnotherPerson.friends.push("Barbie"); 13 14 console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]
Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性
1 var person = { 2 name: "Nicholas", 3 friends: ["Shelby","Court","Van"] 4 }; 5 6 var anotherPerson = Object.create(person,{ 7 name: { 8 value: "Greg" 9 } 10 }); 11 12 console.log(anotherPerson.name); //Greg
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
1 function createAnother(original){ 2 var clone = object(orininal); 3 clone.sayHi = function(){ 4 console.log("hi"); 5 } 6 return clone; 7 }
可以像下面这样使用createAnother()函数
1 function createAnother(original){ 2 var clone = object(orininal); 3 clone.sayHi = function(){ 4 console.log("hi"); 5 } 6 return clone; 7 } 8 9 可以像下面这样使用createAnother()函数 10 11 function object(o){ 12 function F(){} 13 F.prototype = o; 14 return new F(); 15 } 16 17 function createAnother(original){ 18 var clone = object(original); 19 clone.sayHi = function(){ 20 console.log("hi"); 21 } 22 return clone; 23 } 24 25 var person = { 26 name: "Nicholas", 27 friends: ["Shelby","Court","Van"] 28 }; 29 30 var anotherPerson = createAnother(person); 31 anotherPerson.sayHi();//hi
寄生组合式继承
组合继承的最大问题就是无论在什么情况下,都会调用两次超类型函数;
1 function SuperType(name){ 2 this.name = name; 3 this.colors = ["red","blue","green"]; 4 } 5 6 SuperType.prototype.sayName = function(){ 7 console.log(this.name); 8 }; 9 10 function SubperType(name,age){ 11 SuperType.call(this,name); //第二次调用SuperType() 12 this.age =age; 13 } 14 15 SubperType.prototype = new SuperType(); //第一次调用SuperType() 16 SubperType.prototype.constructor = SubperType; 17 SubperType.prototype.sayAge = function(){ 18 console.log(this.age); 19 };
有两组name和colors属性:一组在石丽尚,一组在SubType原型中。这就是调用两次SuperType构造函数的结果解决这个问题的方法就是寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法寄生组合式继承的基本模式如下所示
1 function inheritPrototype(subType,superType){ 2 var prototype = Object(superType.prototype); //创建对象 3 prototype.constructor = subType; //增强对象 4 subType.prototype = prototype; //指定对象 5 }
可以改写上面的例子
1 function SuperType(name){ 2 this.name = name; 3 this.colors = ["red","blue","green"]; 4 } 5 6 SuperType.prototype.sayName = function(){ 7 console.log(this.name); 8 }; 9 10 function SubperType(name,age){ 11 SuperType.call(this,name); 12 this.age =age; 13 } 14 15 inheritPrototype(SubperType,SuperType) 16 SubperType.prototype.constructor = SubperType; 17 SubperType.prototype.sayAge = function(){ 18 console.log(this.age); 19 };
开发人员普遍认为寄生组合式继承时引用类型最理想的继承范式