js中的面向对象程序设计(3)-继承
2016-07-23 22:17
344 查看
继承:
1.接口继承
只继承方法的签名。js中函数没有签名,所以只支持实现继承。
2.实现继承
继承实际的方法,主要依靠原型链来实现。
简单回顾下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
一、原型链实现继承
原理:让一个原型对象等于一个类型的实例。
上述SubType继承SuperType的本质是重写了SubType的原型对象,代之以一个新类型的实例。
SubType的原型被重写了,所以instance.constructor指向的是SuperType.
通过实现原型链,属性的搜索机制扩展如下:
1>搜索实例
2>搜索SubType.prototype
3>搜索SuperType.prototype
下面注意几个问题
1.别忘记默认的原型
所有的引用类型都继承自Object对象,这个继承也是通过原型链实现的。
2.确定原型和实例的关系
*方式一:
instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数。
*方式二:
isPrototypeOf():只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
谨慎地定义方法
子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后。
注意:不能使用对象字面量的形式创建原型方法,因为这样会重写原型链。
//使用字面量添加新方法,会导致上一行代码无效。因为这样又重写了SubType的原型,该原型的constructor现在指向Object构造函数
4.原型链的问题
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。这就又涉及到前面所说的,原型中引用类型属性被所有实例共享的问题了。
原型链的缺点:
1>当父类的构造函数中有引用类型的属性时,在继承的时候,子类的原型实际就变成了父类对象的实例,所以,即子类的原型中包含有引用类型的属性。这样,该引用类型的属性被所有的子类对象实例所共有,这是不可取的。
2>在创建子类实例时,不能向父类的构造函数中传递参数。
所以,很少单独使用原型链。
二、借用构造函数(伪造对象、经典继承)
在子类的构造函数内部调用父类的构造函数。函数只不过是在特定环境中执行代码的对象而已,所以通过使用apply()或者call()方法也可以在新创建的对象上执行构造函数。
需要注意的几个问题:
1.传递参数
借用构造函数的问题
1>方法都在构造函数中定义,无法复用
2>在父类的原型中定义的方法,对子类来说是不可见的。
三、组合继承(伪经典继承)
用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为js中最常用的继承模式。
四、原型式继承
借助原型可以基于已有的对象创建新对象,同时还可以创建自定义类型。
从本质上讲,object()对传入的对象o作了一次浅复制。
ECMAScript 5通过新增Object.create()方法规范化了原型式继承
Object.create()方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
五、寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。
六、寄生组合式继承
组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
其实调用两次SuperType,在SubType的原型中和SubType的实例中都创建了SuperType的实例属性,不过,SubType的实例中的属性会屏蔽其原型中的同名属性。
基于以上缺点,我们找到了解决问题的方法–寄生组合式继承。
即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型
的原型。
这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
1.接口继承
只继承方法的签名。js中函数没有签名,所以只支持实现继承。
2.实现继承
继承实际的方法,主要依靠原型链来实现。
简单回顾下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
一、原型链实现继承
原理:让一个原型对象等于一个类型的实例。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } //继承SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue);//true
上述SubType继承SuperType的本质是重写了SubType的原型对象,代之以一个新类型的实例。
SubType的原型被重写了,所以instance.constructor指向的是SuperType.
通过实现原型链,属性的搜索机制扩展如下:
1>搜索实例
2>搜索SubType.prototype
3>搜索SuperType.prototype
下面注意几个问题
1.别忘记默认的原型
所有的引用类型都继承自Object对象,这个继承也是通过原型链实现的。
2.确定原型和实例的关系
*方式一:
instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数。
alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true
*方式二:
isPrototypeOf():只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true
谨慎地定义方法
子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); //添加新方法 SubType.prototype.getSubValue = function (){ return this.subproperty; }; //重写超类型中的方法,屏蔽SuperType中的已存在的同名方法 SubType.prototype.getSuperValue = function (){ return false; }; var instance = new SubType(); alert(instance.getSuperValue()); //false
注意:不能使用对象字面量的形式创建原型方法,因为这样会重写原型链。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ his.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效。因为这样又重写了SubType的原型,该原型的constructor现在指向Object构造函数
SubType.prototype = { getSubValue : function (){ return this.subproperty; }, someOtherMethod : function (){ return false; } }; var instance = new SubType(); alert(instance.getSuperValue()); //error!
4.原型链的问题
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。这就又涉及到前面所说的,原型中引用类型属性被所有实例共享的问题了。
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //继承了SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
原型链的缺点:
1>当父类的构造函数中有引用类型的属性时,在继承的时候,子类的原型实际就变成了父类对象的实例,所以,即子类的原型中包含有引用类型的属性。这样,该引用类型的属性被所有的子类对象实例所共有,这是不可取的。
2>在创建子类实例时,不能向父类的构造函数中传递参数。
所以,很少单独使用原型链。
二、借用构造函数(伪造对象、经典继承)
在子类的构造函数内部调用父类的构造函数。函数只不过是在特定环境中执行代码的对象而已,所以通过使用apply()或者call()方法也可以在新创建的对象上执行构造函数。
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //继承了SuperType SuperType.call(this); //这里的this表示SubType对象的引用,这样SubType就具有colors属性。 } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
需要注意的几个问题:
1.传递参数
function SuperType(name){ this.name = name; } function SubType(){ //继承了SuperType,同时还传递了参数 SuperType.call(this, "Nicholas"); //实例属性 this.age = 29; } var instance = new SubType(); alert(instance.name); //"Nicholas"; alert(instance.age); //29
借用构造函数的问题
1>方法都在构造函数中定义,无法复用
2>在父类的原型中定义的方法,对子类来说是不可见的。
三、组合继承(伪经典继承)
用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ //继承属性 SuperType.call(this, name); //借用构造函数,对父类的实例属性的继承。 this.age = age; } //继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为js中最常用的继承模式。
四、原型式继承
借助原型可以基于已有的对象创建新对象,同时还可以创建自定义类型。
function object(o){ function F(){} F.prototype = o; return new F(); }
从本质上讲,object()对传入的对象o作了一次浅复制。
var person = { name:'Nicholas', friends:['Shelby','Court','Van'] }; var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob'); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
ECMAScript 5通过新增Object.create()方法规范化了原型式继承
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create()方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); alert(anotherPerson.name); //"Greg"
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
五、寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){ var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 } var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。
六、寄生组合式继承
组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); //第二次调用SuperType() this.age = age; } SubType.prototype = new SuperType(); //第一次调用SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); };
其实调用两次SuperType,在SubType的原型中和SubType的实例中都创建了SuperType的实例属性,不过,SubType的实例中的属性会屏蔽其原型中的同名属性。
基于以上缺点,我们找到了解决问题的方法–寄生组合式继承。
即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型
的原型。
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 } function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){ alert(this.age); };
这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
相关文章推荐
- Extjs4.0 最新最全视频教程
- Javascript中toFixed方法的改进
- 5个常见可用性错误和解决方案
- js数组实现图片轮播
- js可突破windows弹退效果代码
- Lua编程示例(二):面向对象、metatable对表进行扩展
- JSP脚本漏洞面面观
- C#中面向对象编程机制之多态学习笔记
- 使用BAT一句话命令实现快速合并JS、CSS
- 浅谈Lua的面向对象特性
- Lua面向对象之类和继承浅析
- js显示当前星期的起止日期的脚本
- JavaScript面向对象的两种书写方法以及差别
- 浅谈c# 面向对象之类与对象
- C#面向对象特征的具体实现及作用详解
- C# 面向对象的基本原则
- 爆炸式的JS圆形浮动菜单特效代码
- 浅谈对c# 面向对象的理解
- js select常用操作控制代码
- JS实现不使用图片仿Windows右键菜单效果代码