二、继承
OO是面向对象语言最为有魅力的概念。一般的OO语言都实现了两种继承,接口继承和实现继承。接口继承只继承方法签名,而实际继承继承了实际的方法。
而在JS中,函数没有签名,所以无法实现接口继承。只能依靠原型链--实现继承。
2.1原型链
JS中描述了原型链的概念,并利用原型链作为实现继承的主要方法。
其基本思想:利用原型链让一个引用类型继承另一个引用类型的属性和方法。
functionSuperF(){
this.superPropty ='B';
}
SuperF.prototype.getSuperPropty =function(){
alert(this.superPropty);
}
functionSubF(){
this.subPropty ='S';
}
SubF.prototype =newSuperF();
SubF.prototype.constructor =SubF
var p =newSubF();
p.getSuperPropty();
重写子类的原型对象,代之以父类的实例。那么这个实例拥有一个指向父类构造函数原型的指针。
2.1.1别忘记默认的原型
完整的原型链
2.1.2确定原型和实例的关系
确定引用类型是否在原型链中出现过
2.1.3谨慎的定义方法
在子类中定义新方法属性或者重写父类中的方法属性,必须在子类原型被替换之后。
ps:需要注意的一定是再通过原型链实现继承时,不能使用对象字面量添加或重写方法或属性。
这样会使原型再次替换成字面量而导致前面的操作无效。
2.1.4原型链的问题
原型链虽然强大,但是它也带来一些问题:其中最主要的问题来自包含引用类型值的原型。因为包含的引用类型值的原型,其属性会被所有实例共享。所以我们通常把属性写在构造函数中,来避免这个问题。
于是,原型实际上会变成另一个类型的实例,那么原型实例的属性就成了原型属性了。这就会带来问题。
第二个问题是:在创建子类实例的时候,不能向超类型的构造函数中传递参数。这样会影响所有对象实例。
所以,结合上面两个问题,我们通常很少单独使用原型链。
 
2.2借用构造函数
这是一种解决原型中包含引用类型值所带来问题的技术。(又叫做伪造对象
这种技术的基本思想:在子类型构造函数的内部调用超类构造函数。(使用apply()/call()方法)
functionSuperF(){
this.superPropty ='B';
this.colors =['red','blue'];
}
functionSubF(){
SuperF.call(this);
this.subPropty ='S';
}
var p =newSubF();
p.colors.push('black');
p.superPropty ='C'
alert(p.superPropty);//C
alert(p.colors);//'red','blue','black'
var pp =newSubF();
alert(pp.superPropty);//B
alert(pp.colors);//'red','blue'
2.2.1传递参数
functionSuperF(name){
this.name = name;
this.colors =['red','blue'];
}
functionSubF(){
SuperF.call(this,'zjh');
this.subPropty ='S';
}
var p =newSubF();
p.colors.push('black');
alert(p.name);//zjh
alert(p.colors);//'red','blue','black'
var pp =newSubF();
alert(pp.name);//zjh
alert(pp.colors);//'red','blue'
这是借用构造函数模式的一个很大的优势。
ps:为了防止超类构造函数不会重写类型的属性,可以在调用超类型构造函数之后,再添加子类型构造函数的属性。
2.2.2借用构造函数模式的问题
如果仅仅是借用构造函数,那么也无法避免构造函数模式存在的问题----方法都在构造函数中定义,因此函数复用就不可能了。在超类构造函数的原型中定义方法,对子类而言是不可见的,结果所有的类型都只能使用构造函数模式。
所以这种技术也是很少单独使用的。
2.3组合继承
组合继承有时也叫作经典继承。指的是:利用原型模式和借用构造函数模式相结合,发挥两者之长的继承模式。
/**
* 组合继承
*/
functionSuperF(name){
this.name = name;
this.colors =['red','blue'];
}
SuperF.prototype.getName =function(){
alert(this.name+'+function');
}
functionSubF(){
SuperF.call(this,'zjh');
this.subPropty ='S';
}
SubF.prototype =newSuperF();
var p =newSubF();
p.colors.push('black');
alert(p.name);//zjh
alert(p.colors);//'red','blue','black'
p.getName();//zjh+function
var pp =newSubF();
pp.name='zzz'
alert(pp.name);//zjh
alert(pp.colors);//'red','blue'
pp.getName();//zzz+function
组合继承避免了原型链和借用构造函数的缺陷,成为一种JS最常用的继承模式。同时支持instanceof和isPrototypeOf()
2.4原型式继承
这种实现继承的方法没有使用严格意义上的构造函数,而是借用原型,基于已有对象创建新的对象,同时还不必因此创建自定义类型。
    function getObject(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    var o = {
        name : 'zzz',
        age : '15',
        colors: ['red','blue']
    }
    var o1 = getObject(o);
    o1.colors.push('black');
    alert(o1.colors);
    var o2 = getObject(o);

alert(o2.colors);

从何本质上来讲,getObject()对传入其中的对象执行了一次浅复制。
ps:JS也提供了一个函数create(),当它只有一个参数的时候,实现了和上面getObject()方法相同的效果。
var o3 = Object.create(o);
alert(o3.name)  
第二个参数:一个为新对象定义的额外属性的对象
    var o4 = Object.create(o,{
        name : {value:'zsda'}
    });

alert(o4.name)

但是无论用getObject()还是create() ,原型继承所带来的负面影响一定会有(引用类型值在所有创建的对象中共享),但是如果只想让一个对象与另一个对象保持类似的情况下,这种原型集成模式是完全可以胜任的。
2.5寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似。即 创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
   function getObject(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    var o = {
        name : 'zzz',
        age : '15',
        colors: ['red','blue']
    }
    function getAnother(o){
       var clone = getObject(o);  //通过调用一个函数 返回复制的对象
       clone.getName = function(){ //对对象增强
               alert(this.name);
       }
       return clone;
    }
    var o1 = getAnother(o);

o1.getName();

这种方式主要利用于不考虑自定义类型和构造函数的情况下。
它的缺陷:和构造函数模式一样,每创建一次对象,那么它的方法都无法复用,必须创建一个新的方法,效率相对差一些。
2.6寄生组合式继承
上面说过 组合继承是JS中最常见的实用的继承模式,不过它也有自身的不足。
组合继承最大的问题就是:在任何情况下都会调用两次超类型构造函数,一次是在创建子类的原型时候,第二次是在子类型构造函数内部。子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。
    function SuperF(){
        this.name = 'zjh';
        this.age = 15
    }
    SuperF.prototype.getName = function(){
        alert(this.name);
    }
    function SubF(){
        SuperF.call(this); //第二次调用
        this.colors = ['red','blue'];
    }
    SubF.prototype = new SuperF(); //第一次调用
    SubF.prototype.constructor = SubF;
    
    var o = new SubF();

o.getName();

从上面的代码可以看出name和age属性被两次得到,一次在实例上,一次在SubF原型上。这就是组合继承的弊端
解决它的方法就是----寄生组合继承
 
所谓寄生组合继承,就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。主要思路是:
不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
    function object(o){
         function F(){};
         F.prototype = o;
         return new F();
     }
    function inheritPrototype(subF,superF){
        var prototype = object(superF.prototype); //创建超类对象原型的副本
        prototype.constructor = subF;//增强对象
        subF.prototype = prototype;//指定对象
    }
    function SuperF(){
        this.name = 'zjh';
        this.age = 16;
        this.colors = ['red','blue'];
    }
    SuperF.prototype.getName = function(){
        alert(this.name);
    }
    function SubF(){
        SuperF.call(this);
        this.sex = '男';
    }
    inheritPrototype(SubF,SuperF);
    var o = new SubF();
    o.colors.push('black');
    o.getName();
    alert(o.colors);
    var o1 = new SubF();

alert(o1.colors);

这种方式的高效体现在只调用一次SupeF构造函数,并且因此避免了在SubType.prototype上面创建不必要的多余的属性,与此同时,原型链还能保持不变,因此能够正常使用instanceof 和 isPrototypeOf()。这种寄生组合式继承是引用类型最理想的继承范式。
ps:YUI中的YAHOO.lang.extend()方法就采用了这种方式。
三、小结
JS支持面向对象(OO)的编程,但不使用类或者接口来实现。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。
3.1几种创建对象的方法:
工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回一个对象。这个模式因为无法被识别类型,只能被识别为object,而被构造函数模式所取代。
构造函数模式:可以创建自定义的引用类型,可以像创建内置对象
实例一样使用new操作符。不过,它的缺点就是它的每个成员都没办法复用,特别是函数,一方面效率低,一方面函数可以不局限于任何对象,因此没有理由不在多个对象间共享函数。
 
原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式。一般使用构造模式定义实例属性,使用原型模式定义共享的属性和方法。
3.2几种继承方式:
JS主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型来实现的。
原型链继承导致引用类型值会被所有创建的对象共享。
借用构造函数继承(call/apply)导致方法都必须在构造函数内部定义,这样方法的复用就无从谈起了。
组合继承:利用上面两种继承的有点组合起来。缺陷是:会调用两次超类型构造器,从而重复定义属性。
原型式继承(create):它实际上也是原型链继承,问题也是引用类型值会被所有创建的对象共享。(可以不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制,得到复制后的副本可以进一步改造)
寄生式继承:在原型式继承的基础上,基于某个对象创建一个新对象,然后增强对象,返回对象。缺陷和借用构造函数继承一样,方法无法复用共享。
寄生组合式继承:结合寄生式继承和组合继承,解决了两种继承的缺陷,是最有效的方式。
 

JavaScript高级 面向对象的程序设计 (二)《JavaScript高级程序设计(第三版)》的更多相关文章

  1. 轻松学习JavaScript十二:JavaScript基于面向对象之创建对象(二)

    四原型方式 我们创建的每一个函数都有一个通过prototype(原型)属性.这个属性是一个对象,它的用途是包括能够由特定类型 的全部实例共享的属性和方法. 逻辑上能够这么理解:prototypt通过条 ...

  2. 赠书《JavaScript高级程序设计(第三版)》5本

    本站微博上正在送书<JavaScript高级程序设计>走过路过的不要错过,参与方式,关注本站及简寻网+转发微博:http://weibo.com/1748018491/DoCtp6B8r ...

  3. JavaScript高级程序设计第三版.CHM【带实例】

    从驱动全球商业.贸易及管理领域不计其数的复杂应用程序的角度来看,说 JavaScript 已经成为当今世界上最流行的编程语言一点儿都不为过. JavaScript 是一种非常松散的面向对象语言,也是 ...

  4. JavaScript高级程序设计(第三版)学习,第一次总结

    Array类型 var arr = []; arr.length; //返回数组元素个数 改变length可以动态改变数组大小 检测数组 instanceof可以检测某个对象是否是数组,限制:只能是一 ...

  5. JavaScript高级程序设计(第三版)学习笔记20、21、23章

    第20章,JSON JSON(JavaScript Object Notation,JavaScript对象表示法),是JavaScript的一个严格的子集. JSON可表示一下三种类型值: 简单值: ...

  6. JavaScript高级 面向对象(5)--内存逻辑图画法

    说明(2017.3.30): 1. 使用软件diagram designer,DiagramDesignerSetup1.28.zip,很小只有1M多,我用的自带画图软件.教学视频是“JavaScri ...

  7. JavaScript高级程序设计(第三版)学习笔记22、24、25章

    第22章,高级技巧 高级函数 安全的类型检测 typeof会出现无法预知的行为 instanceof在多个全局作用域中并不能正确工作 调用Object原生的toString方法,会返回[Object ...

  8. 10.1.2 Document类型【JavaScript高级程序设计第三版】

    JavaScript 通过Document 类型表示文档.在浏览器中,document 对象是HTMLDocument(继承自Document 类型)的一个实例,表示整个HTML 页面.而且,docu ...

  9. 2.1 <script>元素【JavaScript高级程序设计第三版】

    向 HTML 页面中插入 JavaScript 的主要方法,就是使用<script>元素.这个元素由 Netscape 创造并在 Netscape Navigator 2 中首先实现.后来 ...

  10. JavaScript高级程序设计(第三版) 2/25

    第一章 JavaScript简介 javascript 跟 java没有任何联系,可以这么说,基本上区别就相当于,老婆跟老婆饼.只是因为当初Netscape(js的公司)想搭上媒体热炒的Java的顺风 ...

随机推荐

  1. 关于微信网页调用js-sdk相关接口注意事项目(一级域名与二级域名互相干扰!!!)

    不知道有没有网友遇到过同一个web应用用不同的域名(一级或二级域名)在两个公众号中调用JSSDK相关接口实现功能, 这种做法本来没有问题,问题在于用二级域名(同属一级域名下的二级域名)绑定另一个web ...

  2. Python语言快速入门

    Python的主提示符(>>>):是解释器告诉你它正在等待你输入的下一个语句 Python的次提示符(...):告诉你解释器正在等待你输入当前语句的其他部分 [简介] Python( ...

  3. R %operator% 含义

    %foo% is the syntax for a binary operator. In base R: %in%: '"%in%" <- function(x, tabl ...

  4. linux下批量修改文件名之rename

    最近因为突然用到需匹配更换文件名,发现rename命令真是 简单好用,和sed语法及vim 替换很相似. 1. 更改文件名后缀 rename 's/\.txt/\.html/' * 2.增加文件名后缀 ...

  5. [ActionScript 3.0] AS3 时间格式化方法

    /** * 格式化时间,格式 00:00:00 * @param total 总时间(毫秒) */ function getFormatTime(total:uint):String { if (to ...

  6. sql执行返回值存储

    List<Map> list = SqlRunner.queryMapList(sql); if(list != null && !list.isEmpty()){ Has ...

  7. 异步任务神器 Celery 简明笔记

    转自:http://www.jianshu.com/p/1840035cb510 异步任务 异步任务是web开发中一个很常见的方法.对于一些耗时耗资源的操作,往往从主应用中隔离,通过异步的方式执行.简 ...

  8. 算法库:jpeglib和pnglib安装配置

    类似于OpenCV的安装配置.只不过OpenCV有编译好的,而jpeglib和pnglib需要自己编译.其实,若要跟踪OpenCV的源码或要使用OpenCV的扩展包,OpenCV也得自己编译. Ope ...

  9. cocos2d-x 游戏暂停界面,监听home键,返回键,Menu键 解决方案

    游戏暂停界面: cocos2d-x中游戏暂停界面提供的思路是用pushScene()和popScne(),即推进和弹出场景,当游戏暂停时,推进(pushScene())暂停场景,之前运行的场景将会自动 ...

  10. <%%>与<%#%>与<%=%>

    在asp.net中经常出现包含这种形式<%%>的html代码,总的来说包含下面这样几种格式: 一. <%%> 这种格式实际上就是和asp的用法一样的,只是asp中里面是vbsc ...