zoukankan      html  css  js  c++  java
  • JS继承

      谈到面向对象编程就避不开继承这个概念,JS的继承主要依赖原型链来实现的,今天主要总结一下在JS中的多种继承方式。主要内容如下:

    1. 什么叫原型链
    2. 原型链继承
    3. 经典继承
    4. 组合继承
    5. 原型式继承
    6. 寄生组合式继承
    7. 简析JQuery的Extend函数
    8. 简析ExtJs的Extend函数

    1.什么是原型链

      原型与实例的链状结构叫做原型链。

      构造函数prototype指向一个对象的实例,利用这个构造函数创建实例的时候,实例对象就会拥有原型的属性和方法。如果这个构造函数的原型是另一个构造函数的实例,而另一个构造函数又是别的构造函数的实例,如此层层递进的关系构成实例与原型的链条,我们把这种链状结构叫做原型链。

      JS的继承主要通过原型链来实现的

    2.原型链继承

      原型链继承的结构如下:

     1 function Sup() {
     2     this.name = "sub...";
     3     this.getName = function() {
     4         return this.name;
     5     };
     6 }
     7         
     8 function Sub() {}
     9 Sub.prototype = new Sup();
    10         
    11 var obj = new Sub();
    12 alert(obj.getName());//sub...

      定义了一个超类Sup和一个子类Sub,子类的原型指向超类的实例,所以在通过子类构造的实例也拥有超类的name以及getName方法。当然,如果子类也定义了自己的name属性,调用getName函数返回的将是子类的name。

      构造函数构造函数的时候会自动添加一个构造器(constructor)指向当前的构造函数。所有以构造函数形式产生的原型实例,最后都会指向Objec对象,这也是它们拥有Objec定义的函数的原因。

    3.经典继承

      经典继承又叫借用构造函数,伪造对象等等。主要运用apply与call函数。

      apply与call除了参数有区别外,功能是一样的,都是改变调用者的上下文环境。这里只展示借用构造函数的示例,不详细探讨两个函数的具体细节。

      经典继承的结构如下:

     1 function Sup() {
     2     this.name = "sup...";
     3     this.getName = function() {
     4         return this.name;
     5     };
     6 }
     7 
     8 function Sub() {
     9     Sup.apply(this, arguments);
    10 }
    11 
    12 var obj = new Sub();
    13 alert(obj.getName());//sup...

      定义一个超类Sup和一个子类Sub,其中子类的代码是超类调用apply函数。Sup.apply(this, arguments)表示是在this的环境下执行Sup这个构造函数,效果等同于Sub也拥有了name与getName两个属性。

      需要强调的是它不会继承超类原型中的属性。即在Sup函数下面加一段Sup.prototype.getAge = function() {//...},Sub实例里是访问不到getAge的。

    4.组合继承

      组合继承是引用最广泛的继承方式,它弥补了原型链继承与经典继承的缺点,达到取长补短。

      原型链继承方式主要优点是共享,但这也恰恰成为了它的缺点。我们知道,在JS中对象的求职策略是按引用传递的,这意味着超类定义的对象将会被共享,任何一个用子类构造函数构造的实例修改了该对象,都会反映到其它子类实例中去(当然,超类中的非对象属性没有这种弊端)。

     1 function Sup() {
     2     this.arr = [1, 2, 3];
     3     this.getArr = function() {
     4         return this.arr.toString();
     5     };
     6 }
     7 
     8 function Sub() {}
     9 Sub.prototype = new Sup();
    10 
    11 var obj1 = new Sub();
    12 var obj2 = new Sub();
    13 obj1.arr.push(4);
    14 alert(obj2.getArr());//1,2,3,4

      经典继承不会有原型链的那种弊端,但却无法做倒我们原型设计的初衷——共享。在子类调用构造函数效果等同于在子类中把超类代码重新拷贝一遍,事实上,子类继承的超类函数是完全一样的。

      我们希望需要被共享的属性被共享,不需要被共享的属性不被共享,于是产生了原型链与借用构造函数的组合继承方式。

      组合继承方式如下:

    function Sup() {
        this.arr = [1, 2, 3];//继承后不需要共享的属性
    }
    Sup.prototype.getArr = function() {//借用构造函数不会继承超类原型里的东西
        return this.arr.toString();
    };
    
    function Sub() {
        Sup.apply(this);
    }
    Sub.prototype = new Sup();
    
    var obj1 = new Sub();
    var obj2 = new Sub();
    obj1.arr.push(4);
    alert(obj1.getArr());//1,2,3,4
    alert(obj2.getArr());//1,2,3

      组合继承足以应对各种继承场景了,但是道格拉斯.克罗克福德提出了一种基于已有的类型创建对象的继承方式——原型式继承。

    5.原型式继承

      原型式继承在ECMAJavascript 5中被定义到Object对象中的create中。

      原型式继承实现思路是定义一个创建函数,将原型对象作为参数,该函数内部封装了继承细节。

      其实现大致如下:

     1 function create(proto) {
     2     function F() {}
     3     F.prototype = proto;
     4     return new F();
     5 }
     6 
     7 var Book = {publish : "xx出版社"};
     8 
     9 var book = create(Book);
    10 alert(book.publish);//xx出版社

      EcmaJavascript5中只需把上面的create函数换成Object.create()即可,这个函数的第一个参数是原型对象,第二个参数是额外的属性对象用以控制属性对应内置属性。这里不做细节描述。

    6.寄生组合式继承

      寄生组合式继承是对组合式继承的优化,因为组合式继承会执行两次构造函数(创建子类原型时、借用构造函数时)。

      寄生组合式继承把继承原型的步骤放在原型式继承中进行。

      其代码结构如下:

    function inheritprototype(sub, sup) {
        var prototype = Object.create(sup.prototype);
        prototype.constructor = sub;//构造器
        sub.prototype = prototype;
    }
    
    function SupObject() {
        this.name = arguments[0] || "超类";
    }
    SupObject.prototype.getName = function() {
        return this.name;
    }
    
    function SubObject() {
        SupObject.apply(this, arguments);
    }
    inheritprototype(SubObject, SupObject);
    
    var s = new SubObject("子类");
    alert(s.getName());//子类

    7.JQuery中的Extend函数

      JQuery的继承有个特点:它有个可选参数(deep),用来标识执行浅复制或者深复制。

      用法:JQuery.extend([是否深度复制], 返回的目标对象, [扩展的对象...]);

      深复制与浅复制的区别在于前者会遍历同名属性的值,并区别复制。

      浅复制:扩展对象里的属性与目标对象的属性同名,则直接把拓展对象的属性值赋值给目标对象对应的属性。比如说目标对象是{a:{b:'b', c:'c'}},拓展对象是{a:{d:'d'}},执行浅复制则返回{a:{d:'d'}}。

      深复制:扩展对象里的属性与目标对象的属性同名,则遍历扩展对象(递归的过程),执行属性的复制。比如上面那个例子返回的结果是{a:{b:'b', c:'c',d:'d'}}。

    //浅复制
    function copy() {
        var target = arguments[0],
            config = arguments[1];
        for(var prop in config) {
            target[prop] = config[prop];
        }
        return target;
    }
    
    var target = {
        a : {
            b : 'b', 
            c : 'c'
        }
    }
    var config = {
        a : {
            d : 'd'
        }
    }
    
    var result = copy(target, config);//{a:{d:'d'}}
    //深复制
    function copy() {
        var target = arguments[0],
            config = arguments[1];
        for(var prop in config) {
            if(typeof target[prop] === 'object') {
                var temp;
                if(Object.prototype.toString.call(target[prop]) === '[object Array]') {
                    temp = target[prop] || [];
                }else {
                    temp = target[prop] || {};
                }
                target[prop] = copy(temp, config[prop]);
            }else {
                target[prop] = config[prop];
            }
        }
        return target
    }
    
    var target = {
        a : {
            b : 'b', 
            c : 'c'
        }
    }
    var config = {
        a : {
            d : 'd'
        }
    }
    
    var result = copy(target, config);//{a:{b:'b', c:'c',d:'d'}}

      在深复制代码里首先用typeof操作符把基本类型(string、boolean、number) 与引用类型区分开来,基本类型拷贝是覆盖(与浅复制相同),需要注意的是引用类型中的数组要给它声明一个空数组。

      在JQuery源码中代码复杂些,但功能也更强大。它增加了防循环引用机制、深复制时拒绝window、DOM、原型继承而来的复杂对象。

    8.ExtJs中的Extend

      ExtJs中的apply函数其实就是一种浅复制,而它的applyIf是一种忽略目标对象已有的属性。

      ExtJs的extend函数功能比较复杂,大致做了如下几种工作:

    • 确认子类型
    • 原型链继承
    • 给子类赋予覆盖函数(override)
    • 扩展对象对子类进行覆盖操作
    • 重写子类的继承函数

      1)确认子类型:Ext.extend函数有两种传参方式,三个参数的时候,从左到右传递的分别是子类、超类、扩展对象;两个参数的时候分别是超类与扩展对象。两种传参方式让人用起来很方便其内部实现大致如下:

    function confirSub(sub, sup, config) {
        if(!!sup && Object.prototype.toString.call(sup) === '[object Object]') {
            config = sup;
            sup = sub;
            if(sp.constructor != Object.prototype.constructor) {
                sub = sup.constructor;
            }else {
                sup.apply(this, arguments);
            }
        }
    }

      上面代码主要是处理两个参数情况,当第二个参数是对象字面量(扩展对象)时,把它当只传递两条参数处理。然后扩展对象、超类、子类进行重新定位,以便后文与传三个参数的情况统一。其中超类如果是新建对象,则子类用经典继承方式确认,如果超类是通过某些继承机制产生的对象,则将超类的构造器赋给子类。

      2)原型链继承:这个继承代码与寄生组合式继承原型中函数的代码很像。代码大致如下:

    function extendProto(sub, sup, config) {
        var F = function() {},
            sbp,//用来指代子类的原型
            spp = sup.prototype;//指代超类的原型
        F.prototyoe = spp;
        sbp = sub.prototype = new F();
        sbp.constructor = sub;
        //在控件子类继承中,扩展对象里的构造函数常调用它,
        //调用方式是:新类型.supperclass.constructor.call(this,arguments)
        sub.supperclass = spp;
    }

      3)子类新增覆盖函数(override),覆盖函数与JQuery浅复制类似,这里就不赘言了。

      4)扩展对象对子类进行浅复制:源码里就一行语句Ext.override(sb, overrides);

      5)重写子类extend函数:源码里也是一行解决sb.extend = function(o) {return Ext.extend(sb, o);};其作用是子类的子类只能把当前子类作为超类。

     

  • 相关阅读:
    [转]Could not load file or assembly 'XXX' or one of its dependencies.
    网页上显示别人电脑没安装的字体,例如LED字体
    JS 保留小数点后面2位小数
    ASP.NET2.0揭秘读书笔记五——维护应用程序状态之cookie
    C#高级编程读书笔记之.NET体系结构
    ASP.NET2.0揭秘读书笔记之八——页面输出缓存
    《大话设计模式》读书笔记一 简单工厂模式
    C#高级编程读书笔记之继承
    ASP.NET 2.0揭秘读书笔记七——使用用户配置文件Profile
    终于成功安装了SQL SqlServer2005
  • 原文地址:https://www.cnblogs.com/longhx/p/5404692.html
Copyright © 2011-2022 走看看