javascript面向对象系列第三篇——实现继承的3种形式
前面的话
学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承。本文是javascript面向对象系列第三篇——实现继承的3种形式
【1】原型链
javascript使用原型链作为实现继承的主要方法,实现的本质是重写原型对象,代之以一个新类型的实例
function Super(){
this.value = true;
}
Super.prototype.getValue = function(){
return this.value;
};
function Sub(){}
//Sub继承了Super
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var instance = new Sub();
console.log(instance.getValue());//true
原型链最主要的问题在于包含引用类型值的原型属性会被所有实例共享,而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了
function Super(){
this.colors = ['red','blue','green'];
}
function Sub(){};
//Sub继承了Super
Sub.prototype = new Super();
var instance1 = new Sub();
instance1.colors.push('black');
console.log(instance1.colors);//'red,blue,green,black'
var instance2 = new Sub();
console.log(instance2.colors);//'red,blue,green,black'
【1.1】原型式继承
原型式继承借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。从本质上讲,object()对传入其中的对象执行了一次浅复制
function object(o){
function F(){};
F.prototype = o;
return new F();
}
var superObj = {
colors: ['red','blue','green']
};
var subObj1 = object(superObj);
subObj1.colors.push("black");
var subObj2 = object(superObj);
subObj2.colors.push("white");
console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]
实际上,Object.create()方法规范化了原型式继承
var superObj = {
colors: ['red','blue','green']
};
var subObj1 = Object.create(superObj);
subObj1.colors.push("black");
var subObj2 = object(superObj);
subObj2.colors.push("white");
console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]
[注意]原型式继承虽然只是看上去将原型链继承的一些程序性步骤包裹在函数里而已,与原型链继承有着引用类型值的问题。但是,它们的一个重要区别是父类型的实例对象不再作为子类型的原型对象
1、使用原型链继承
function Super(){
this.value = 1;
}
Super.prototype.value = 0;
function Sub(){};
//将父类型的实例对象作为子类型的原型对象
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
//创建子类型的实例对象
var instance = new Sub;
console.log(instance.value);
2、使用原型式继承
function Super(){
this.value = 1;
}
Super.prototype.value = 0;
function Sub(){};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
//创建子类型的实例对象
var instance = new Sub;
console.log(instance.value);
上面的Object.create函数一行代码Sub.prototype = Object.create(Super.prototype)可以分解为
function F(){};
F.prototype = Super.prototype;
Sub.prototype = new F();
由上面代码看出,子类的原型对象是临时类F的实例对象,而临时类F的原型对象又指向父类的原型对象;所以,实际上,子类可以继承父类的原型上的属性,但不可以继承父类的实例上的属性
【1.2】寄生式继承
寄生式继承创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象
function parasite(original){
var clone = Object.create(original);//通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
console.log("hi");
};
return clone;//返回这个对象
}
var superObj = {
colors: ['red','blue','green']
};
var subObj1 = parasite(superObj);
subObj1.colors.push('black');
var subObj2 = parasite(superObj);
subObj2.colors.push('white');
console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]
[注意]寄生式继承实际上只是原型式继承的再包装,与原型式继承有着同样的问题,且由于不能做到函数复用而降低了效率
【2】借用构造函数
借用构造函数(constructor stealing)的技术(有时候也叫做伪类继承或经典继承)。基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数,通过使用apply()和call()方法在新创建的对象上执行构造函数
function Super(){
this.colors = ['red','blue','green'];
}
function Sub(){
//继承了Super
Super.call(this);
}
var instance1 = new Sub();
instance1.colors.push('black');
console.log(instance1.colors);// ['red','blue','green','black']
var instance2 = new Sub();
console.log(instance2.colors);// ['red','blue','green']
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数
function Super(name){
this.name = name;
}
function Sub(){
//继承了Super,同时还传递了参数
Super.call(this,"bai");
//实例属性
this.age = 29;
}
var instance = new Sub();
console.log(instance.name);//"bai"
console.log(instance.age);//29
但是,如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了
【3】组合继承
组合继承(combination inheritance)有时也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性
function Super(name){
this.name = name;
this.colors = ['red','blue','green'];
}
Super.prototype.sayName = function(){
console.log(this.name);
};
function Sub(name,age){
//继承属性
Super.call(this,name);
this.age = age;
}
//继承方法
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
console.log(this.age);
}
var instance1 = new Sub("bai",29);
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();
var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
instance2.sayAge();
组合继承有它自己的问题。那就是无论什么情况下,都会调用两次父类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。子类型最终会包含父类型对象的全部实例属性,但不得不在调用子类型构造函数时重写这些属性
function Super(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){
return this.name;
};
function Sub(name,age){
// 第二次调用Super(),Sub.prototype又得到了name和colors两个属性,并对上次得到的属性值进行了覆盖
Super.call(this,name);
this.age = age;
}
//第一次调用Super(),Sub.prototype得到了name和colors两个属性
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
return this.age;
};
【3.1】寄生组合式继承
寄生组合式继承与组合继承相似,都是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。只不过把原型继承的形式变成了寄生式继承。使用寄生组合式继承可以不必为了指定子类型的原型而调用父类型的构造函数,从而寄生式继承只继承了父类型的原型属性,而父类型的实例属性是通过借用构造函数的方式来得到的
function parasite(original){
var clone = Object.create(original);//通过调用函数创建一个新对象
return clone;//返回这个对象
}
function Super(name){
this.name = name;
this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){
return this.name;
};
function Sub(name,age){
Super.call(this,name);
this.age = age;
}
Sub.prototype = parasite(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
return this.age;
}
var instance1 = new Sub("bai",29);
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();
var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
instance2.sayAge();
最后
继承这块可能是ECMAScript中最难理解的部分。如果说作用域和this机制的难在于绕,则这部分的难则在于混杂。每种模式都有自己的优点,而多个模式结合在一起就可能造成一些属性的重置,这是最需要注意的地方
更多的模式都是为了更好的解决问题。学习原理时学的深一点,解决问题时才能更顺利点
以上
// 0){
return;
}
if(select[i].getBoundingClientRect().top 0){
change(oCon.children[i+2])
}
}else{
change(oCon.children[select.length+1])
}
}
}
document.body.onmousewheel = wheel;
document.body.addEventListener('DOMMouseScroll',wheel,false);
var oCon = document.getElementById("content");
var close = oCon.getElementsByTagName('span')[0];
close.onclick = function(){
if(this.innerHTML == '显示目录'){
this.innerHTML = '×';
this.style.background = '';
oCon.style.border = '2px solid #ccc';
oCon.style.width = '';
oCon.style.height = '';
oCon.style.overflow = '';
oCon.style.lineHeight = '30px';
}else{
this.innerHTML = '显示目录';
this.style.background = '#3399ff';
oCon.style.border = 'none';
oCon.style.width = '60px';
oCon.style.height = '30px';
oCon.style.overflow = 'hidden';
oCon.style.lineHeight = '';
}
}
for(var i = 2; i
javascript面向对象系列第三篇——实现继承的3种形式的更多相关文章
- 深入理解javascript函数系列第三篇——属性和方法
× 目录 [1]属性 [2]方法 前面的话 函数是javascript中的特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样.甚至可以用Function()构造函数来创建新的函数对象.本 ...
- 深入理解javascript函数系列第三篇
前面的话 函数是javascript中特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样.甚至可以用Function()构造函数来创建新的函数对象.本文是深入理解javascript函数 ...
- 深入理解javascript作用域系列第三篇——声明提升(hoisting)
× 目录 [1]变量 [2]函数 [3]优先 前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javasc ...
- 深入理解javascript作用域系列第三篇
前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javascript作用域系列第三篇——声明提升(hois ...
- javascript面向对象系列第四篇——选项卡的实现
前面的话 面向对象的应用并非只是读几本书那么容易,需要有大量的工程实践做基础才能真正理解并学会使用它.本文将用面向对象的技术来制作一个简单的选项卡 图示说明 由图示结果看到,这是一个非常简单的选项卡. ...
- javascript面向对象系列第四篇——OOP中的常见概念
前面的话 面向对象描述了一种代码的组织结构形式——一种在软件中对真实世界中问题领域的建模方法.本文将从理论层面,介绍javascript面向对象程序程序(OOP)中一些常见的概念 对象 所谓对象,本质 ...
- 深入理解javascript对象系列第三篇——神秘的属性描述符
× 目录 [1]类型 [2]方法 [3]详述[4]状态 前面的话 对于操作系统中的文件,我们可以驾轻就熟将其设置为只读.隐藏.系统文件或普通文件.于对象来说,属性描述符提供类似的功能,用来描述对象的值 ...
- javascript动画系列第三篇——碰撞检测
前面的话 前面分别介绍了拖拽模拟和磁性吸附,当可视区域内存在多个可拖拽元素,就出现碰撞检测的问题,这也是javascript动画的一个经典问题.本篇将详细介绍碰撞检测 原理介绍 碰撞检测的方法有很多, ...
- 深入理解javascript选择器API系列第三篇——h5新增的3种selector方法
× 目录 [1]方法 [2]非实时 [3]缺陷 前面的话 尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展.人们对jQuer ...
随机推荐
- 案例1.用Ajax实现用户名的校验
用Ajax实现用户名的校验 java的验证类 public class UserDao { public boolean checkUserName(String name) { //这里的name是 ...
- 【Telerik】<telerik:RadComboBox>导出列表数据
近来在做项目,做到导出功能.使用<telerik:RadComboBox>的下拉框来实现导出部分或导出所有数据的功能.
- APP设计资源
在开发独立客户端时,需要一些不同尺寸的图标和图片,统计如下. APP 图标 ICON iOS:(主要需要这三类图标) 58x58 87x87 (Spotlight & Settings) 80 ...
- JQM开发Tips
1.radio Button 点击后有时候有高亮样式,有时候没有 解决方案: $("#task_form label").click(function () { $("# ...
- easyui-conbotree树形下拉框。。。转
最近一直在研究这个树形的下拉选择框,感觉非常的有用,现在整理下来供大家使用: 首先数据库的表架构设计和三级菜单联动的表结构是一样,(父子关系) 1.下面我们用hibernate建一下对应的额实体类: ...
- Probe在性能测试中的使用方式简介
简介: Lambda Probe(以前称为Tomcat Probe)是一款实时监控和管理的Apache Tomcat实例的基本工具. Lambda Probe 是基于 Web + AJAX 的强大的免 ...
- php函数类型
静态变量: <?php function calcute(){ static $num =10; $num = $num+2; echo $num."<br>"; ...
- Twitter面试题蓄水池蓄水量算法(原创 JS版,以后可能会补上C#的)
之前在群里有人讨论Twitter的面试题,蓄水池蓄水量计算,于是自己写了个JS版的(PS:主要后台代码还要编译,想想还是JS快,于是就使用了JS了.不过算法主要还是思路嘛,而且JS应该都没问题吧^_^ ...
- 读取XML直接转换为类对象
<?xml version="1.0" encoding="utf-8"?> <ArrayOfMenuItems xmlns:xsi=&quo ...
- 非常全面的SQL Server巡检脚本来自sqlskills团队的Glenn Berry 大牛
非常全面的SQL Server巡检脚本来自sqlskills团队的Glenn Berry 大牛 Glenn Berry 大牛会对这个脚本持续更新 -- SQL Server 2012 Diagnost ...