• javascript继承


    JavsScript中对象继承关系变得无关紧要,对于一个对象来说重要的是它能做什么,而不是它从哪里来。

    JavaScript提供了一套更为丰富的代码重用模式。它可以模拟那些基于类的模式,同时它也可以支持其他更具表现力的模式。

    JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。

    一、伪类

    1、原理

    javascript原型机制:不直接让对象从其他对象继承,反而插入了一个多余的间接层:通过构造器函数产生对象。

    当一个函数对象被创建时,Function构造器产生的函数对象会运行类型这样一些代码:

    this.prototype={constructor:this}

    新函数对象被赋予一个prototype属性,它的值是一个包含constructor属性且属性值为该新函数的对象。这个prototype对象是存放继承特征的地方。

    当采用构造器调用模式,即用new前缀去调用一个函数时,函数执行的方式会被修改。如果new运算符是一个方法而不是一个运算符,它可能会像这样执行:

    Function.method('new',function () {
        //创建一新对象,它继承自构造器函数的原型对象。
        var that=Object.create(this.prototype);
        //调用构造器函数,绑定-this-到新对象上。
        var other=this.apply(that,arguments);
        //如果它的返回值不是一个对象,就返回该对象。
        return (typeof other==='object'&&other)||that;
    });

    2、伪类,即使用new前缀

    定义一构造器并扩充它的原型:

    var Mammal=function(name){
        this.name=name;
    }
    Mammal.prototype.get_name=function(){
        return this.name;
    }
    Mammal.prototype.says=function(){
        return this.saying || '';
    }

    现在构造一个实例:

    var myMammal=new Mammal('Herb the Mammal');
    var name=myMammal.get_name();//"Herb the Mammal"

    构造另一个伪类继承Mamal,这是通过定义它的constructor函数并替换它的prototype为一个Mammal的实例来实现的。

    var Cat=function(name){
        this.name=name; //重复实现了一遍
        this.saying='meow';
    }
    //替换cat.prototype为一个新的Mammal实例
    Cat.prototype=new Mammal();
    //扩充新原型对象,增加purr和get_name方法。
    Cat.prototype.purr=function(n){
        var i,s='';
        for(i=0;i<n;i+=1){
            if(s){
                s+='-'
            }
            s+='r';
        }
        return s;
    }
    Cat.prototype.get_name=function(){
        return this.says()+' '+this.name+' '+this.says();
    }
    
    var myCat=new Cat('Henrietta');
    var says=myCat.says();//"meow"
    var purr=myCat.purr(5);//"r-r-r-r-r"
    var name=myCat.get_name();//"meow Henrietta meow"

    伪类模式本意是想向面向对象靠拢,但它看起来格格不入。

    我们隐藏一些丑陋的细节,通过使用method方法来定义一个inherits方法实现。

    Function.prototype.method=function(name,func){
        if(!this.prototype[name]){
            this.prototype[name]=func;
        }
        return this;
    }
    
    Function.method('inherits',function(Parent){
        this.prototype=new Parent();
        return this;
    });
    
    var Cat=function(name){
        this.name=name;
        this.saying='meow'
    }
    .inherits(Mammal)
    .method('purr',function(n){
        var i,s='';
        for(i=0;i<n;i+=1){
            if(s){
                s+='-'
            }
            s+='r';
        }
        return s;
    })
    .method('get_name',function(){
        return this.says()+' '+this.name+' '+this.says();
    });
    
    var myCat=new Cat('Henrietta');
    var says=myCat.says();//"meow"
    var purr=myCat.purr(5);//"r-r-r-r-r"
    var name=myCat.get_name();//"meow Henrietta meow"

    问题:以上虽然隐藏了prototype操作细节,但是问题还在:有了像“类” 的构造器函数,但仔细看它们,你会惊讶地发现:

    1、没有私有环境,所有的属性都是公开的。

    2、使用构造器函数存在一个严重的危害。如果调用构造函数时忘记了在前面加上new前缀,那么this将不会被绑定到一个新对象上。悲剧的是,this将被绑定到全局对象上,所以你不但没有扩充新对象,反而破坏了全局变量环境。

    这是一个严重的语言设计错误。为了降低这个问题带来的风险,所有的构造器函数都约定命名成首字母大写的形式,并且不以首字母大写的形式拼写任何其他的东西。

    一个更好的备选方案就是根本不使用new。

    二、对象说明符

    构造器要接受一大串参数,要记住参数的顺序非常困难。所以编写构造器时让它接受一个简单的对象说明符,更友好。

    //接受一大串参数
    var myObject=maker(f,l,m,c,s);
    
    //对象字面量更友好
    var myObject=maker({
        first:f,
        middle:m,
        last:l,
        state:s,
        city:c
    });

    对象字面量好处:

    • 多个参数可以按任何顺序排列
    • 构造器如果聪明的使用了默认值,一些参数可以忽略掉
    • 和JSON一起用时,可以把JSON对象传给构造器,返回一个构造完全的对象

    三、原型

    原型模式中,摒弃类,转而专注于对象。概念:一个新对象可以继承一个旧对象的属性。 通过构造一个有用的对象开始,接着可以构造更多和那个对象类似的对象。这就可以完全避免把一个应用拆解成一系列嵌套抽象类的分类过程。

    1、差异化继承

    用对象字面量构造一个有用的对象。

    var myMammal={
        name:"Herb the Mammal",
        get_name:function(){
            return this.name;
        },
        says:function(){
            return this.saying || '';
        }
    }

    一旦有了想要的对象,就可以利用Object.create方法构造出更多的实例。

    var myCat=Object.create(myMammal);
    myCat.name='Henrietta';
    myCat.saying='meow';
    myCat.purr=function(n){
        var i,s='';
        for(i=0;i<n;i++){
            if(s){
                s+='-';
            }
            s+='r';
        }
        return s;
    }
    myCat.get_name=function(){
        return this.says+' '+this.name+' '+this.says;
    }

    这是一种“差异化继承(differential inheritance)”,通过定制一新的对象,我们指明它与所基于的基本对象的区别。

    2、差异化继承优势

    差异化继承,对某些数据结构继承于其他数据结构的情形非常有用。

    例:假定我们要解析一门类似JavaScript这样用一对花括号指示作用域的语言。定义在某个作用域里的条目在该作用域外是不可见的。

    但在某种意义上,一个内部作用域会继承它的外部作用域。JavaScript在表示这样的关系上做得非常好。

    当遇到一个左花括号时block函数被调用,parse函数将从scope中寻找符号,并且它定义了新的符号时扩充scope。

    var block=function(){
        //记住当前的作用域。构造一包含了当前作用域中所有对象的新的作用域
        var oldScope=scope;
        scope=Object.create(scope);
        //传递左花括号作为参数调用advance
        advance('{');
        //使用新的作用域进行解析
        parse(scope);
        //传递右花括号作为参数调用advance并抛弃新作用域,恢复原来老的作用域
        advance('}');
        scope=oldScope;
    }

    四、函数化

    至此,上面我们看到的继承模式的一个弱点就是:没法包含隐私。对象的所有属性都是可见的。

    应用模块模式,可以解决这个问题。

    1、模块模式

    从构造一个生成对象的函数开始。我们以小写字母开头来命名它,因为它并不需要使用new前缀。该函数包括4个步骤:

    1. 创建一个新对象。有很多的方式去构造一个对象
      • 对象字面量构造
      • new调用一个构造器函数
      • Object.create方法构造一个已经尽的对象的新实例
      • 调用任意一个会返回一个对象的函数
    2. 有选择性地定义私有实例变量和方法。这些就是函数中通过var 语句定义的普通变量。
    3. 给这个新对象扩充方法。这些方法拥有特权去访问参数以及在第二步中通过var语句定义的变量
    4. 返回那个新对象。

    下面是一个函数化构造器的伪代码模板(加粗的文本表示强调):

    var constructor =function(spec,my){
        var that,其他私有实例变量;
        my=my||{};
        把共享的变量和函数添加到my中
        that=一个新对象
        添加给that的特权方法
        return that;
    }

    说明:

    spec对象包含构造器需要构造一新实例的所有信息。spec的内容可能被复制到私有变量中,或者被其他函数改变,或者方法可以在需要的时候访问spec的信息。(一个简化的方式是替换spec为一个单一的值。当构造对象过程汇总并不需要整个spec对象的时候,这是有用的)

    my对象是一个为继承链中的构造器提供秘密共享的容器。 my对象可以选择性地使用。如果没有传入一个my对象,那么会创建一个my对象。

    接下来,声明该对象私有的实例变量和方法。 通过简单的声明变量就可以做到。构造器的变量和内部函数变成了该实例的私有成员。内部函数可以访问spec,my,that,以及其他私有变量。

    接下来,给my变量添加共享的秘密成员。这是通过赋值语句来实现的:

    my.member=value;

    现在,我们构造了一个新对象并把它赋值给that。构造新对象可能是通过调用函数化构造器,传给它一个spec对象(可能就是传递给当前构造器的同一个spec对象)和my对象。my对象允许其他的构造器分享我们放到my中的资料。其他的构造器可能也会把自己可分享的秘密成员放进my对象里,以便我们的构造器可以利用它。

    接下来,扩充that,加入组成该对象接口的特权方法。我们可以分配一个新函数称为that的成员方法。或者,更安全地,我们可以先把函数定义为私有方法,然后再把它们分配给that

    var methodical=function(){

    ...

    };

    that.methodical=methodical;

    /*分开两步去定义methodical的好处是,如果其他方法想要调用methodical,它们可以直接调用methodical()而不是that.methodical()。
    如果该实例被破坏或篡改,甚至that.methodical被替换掉了,
    调用methodical的方法同样会继续工作,因为它们私有的methodical不受该实例被修改的影响。*/

    2、应用

    我们把这个模式应用到mammal例子里。此处不需要my,所以我们先抛开它,但会使用一个spec对象。

    var mammal=function(spec){
        var that={};
        that.get_name=function(){
            return spec.name;
        };
        that.says=function(){
            return spec.saying || '';
        }
        return that;
    }
    
    var myMammal=mammal({name:'Herb'});

     此时name就是私有属性,被保护起来了。

    在伪类模式里,构造器函数Cat不得不重复构造器Mammal已经完成的工作。在函数化模式中那不再重要了,因为构造器Cat将会调用构造器Mammal,让Mammal去做对象创建中的大部分工作,所以Cat只需关注自身的差异即可。

    var cat=function(spec){
        spec.saying=spec.saying || 'meow';
        var that=mammal(spec);
        that.purr=function(n){
            var i,s='';
            for(i=0;i<n;i++){
                if(s){
                    s+='-';
                }
                s+='r';
            }
            return s;
        };
        that.get_name=function(){
            return that.says()+' '+spec.name+' '+that.says();
        };
        return that;
    }
    
    var myCat=cat({name:'Henrietta'});

     函数化模式还给我们提供了一个处理父类方法的方法。

    我们会构造一个superior方法,它取得一个方法名并返回调用那个方法的函数。该函数会调用原来的方法,尽管属性已经变化了。

    /*有点难理解*/

    Object.method('superior',function(name){ //传入方法名name
        var that=this,method=that[name]; 
        return function(){
            return method.apply(that,argumetns);
        }
    });

    把调用superior应用在coolcat上, coolcat就像cat一样,除了它有一个更酷的调用父类cat的方法的get_name方法。

    它只需要一点点准备工作。我们会声明一个super_get_name变量,并且把调用superior方法所返回的结果赋值给它。

    var coolcat=function(spec){  //coolcat有一个更酷的调用父类cat的方法的get_name方法
        var that=cat(spec);
        var super_get_name=that.superior('get_name');
        that.get_name=function(n){
            return 'like '+super_get_name()+'baby';
        }
        return that;
    }
    
    var myCoolCat=coolcat({name:'Bix'});
    var name=myCoolCat.get_name();//"like meow Bix meowbaby"

    函数模块化有很大的灵活性。它相比伪类模式不仅带来的工作更少,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

    /*有点难理解*/

    如果对象的所有状态都是私有的,那么该对象就称为了一个“防伪(tamper-proof)对象” 。该对象的属性可以被替换或删除,但该对象的完整性不会受到损害。

    如果我们用函数化的模式创建一个对象,并且该对象的所有方法都不使用this或that,那么该对象就是持久性(durable)的。

    一个持久性的对象不会被入侵。访问一个持久性的对象时,除非有方法授权,否则攻击者不能访问对象的内部状态。

    总结一下以上整个完美的继承链的代码:

    <script>
    /* *****mammal object***** */
    var mammal=function(spec){
        var that={};
        that.get_name=function(){
            return spec.name;
        };
        that.says=function(){
            return spec.saying || '';
        }
        return that;
    }
    //call
    var myMammal=mammal({name:'Herb'});
    
    /* *****cat object***** */
    var cat=function(spec){
        spec.saying=spec.saying || 'meow';
        var that=mammal(spec);
        that.purr=function(n){
            var i,s='';
            for(i=0;i<n;i++){
                if(s){
                    s+='-';
                }
                s+='r';
            }
            return s;
        };
        that.get_name=function(){
            return that.says()+' '+spec.name+' '+that.says();
        };
        return that;
    }
    //call
    var myCat=cat({name:'Henrietta'});
    
    /*user-defined Method*/
    Function.prototype.method=function(name,func){
        if(!this.prototype[name]){
            this.prototype[name]=func;
        }
        return this;
    }
    
    Object.method('superior',function(name){ //传入方法名name
        var that=this,method=that[name]; 
        return function(){
            return method.apply(that,arguments);
        }
    });
    
    /* *****coolcat object***** */
    var coolcat=function(spec){  //coolcat有一个更酷的调用父类cat的方法的get_name方法
        var that=cat(spec);
        var super_get_name=that.superior('get_name');
        that.get_name=function(n){
            return 'like '+super_get_name()+'baby';
        }
        return that;
    }
    //call
    var myCoolCat=coolcat({name:'Bix'});
    var name=myCoolCat.get_name();//"like meow Bix meowbaby"
    </script>
    View Code

    五、部件(Parts)

    我们可以从一套部件中把对象组装出来。

    例如,我们可以构造一个给任何对象添加简单事件处理特性的函数。它会给对象添加一个on方法,一个fire方法和一个私有的事件注册表对象:

    <script>
    var eventuality=function(that){
        var registry={};  //注册表
        that.fire=function(event){
            //在一个对象上触发一个事件。该事件可以是一个包含事件名称的字符串,
            //或者是一个拥有包含事件名称的type属性的对象。
            //通过'on'方法注册的事件处理程序中匹配事件名称的函数将被调用
            var array,
                  func,
                  handler,
                  i,
                  type=typeof event ==='string'?event:event.type;
             //如果这个事件存在一组事件处理程序,那么就遍历它们并按顺序依次执行。
             if(registry.hasOwnProperty(type))     {
                array=registry[type];
                for(i=0;i<array.length;i++){
                    handler=array[i];
                    //每个处理程序包含一个方法和一组可选的参数。
                    //如果该方法是一个字符串形式的名称,那么寻找到该函数。
                    func=handler.method;
                    if(typeof func==='string'){
                        func=this[func];
                    }
                    //调用一个处理程序。如果该条目包含参数,那么传递它们过去。否则,传递该事件对象。
                    func.apply(this,handler.paramenters || [event]);
                }
             }
             return this;
        }
    
        that.on=function(type,method,parameters){
            //注册一个事件。构造一条处理程序条目。将它插入到处理程序数组中,
            //如果这种类型的事件还不存在,就构造一个。
            var handler={
                method:method,
                parameters:parameters
            };
            if(registry.hasOwnProperty(type)){
                registry[type].push(handler);
            }else{
                registry[type]=[handler];
            }
            return this;
        }
        return that;
    }
    </script>

    我们可以在任何单独的对象上调用eventuality,授予它事件处理方法。 我们也可以赶在that被返回前在一个构造器函数中调用它。eventlity(that);

    用这种方式,一个构造器函数可以从一套布局中把对象组装出来。JavaScript的弱类型在此处是一个巨大的优势,因为我们无须花费精力去了解对象在类型系统中的继承关系。相反,我们只需要专注于它们的个性特征。

    如果我们想要eventuality访问该对象的私有状态,可以把私有成员集my传递给它。

    参考:

    https://www.zybuluo.com/zhangzhen/note/77227

    本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/4904929.html有问题欢迎与我讨论,共同进步。

  • 相关阅读:
    JQuery对象与Dom对象相互转化
    JQuery练习demo2
    ExtJs简单的登录界面制作
    JQuery练习demo1(隔行变色)
    html标签label的for属性
    Android环境搭建(Windows)
    JQuery表格操作练习
    ExtJs简单动态ComboBox
    Asp.Net母版页的使用
    SQL经典语句大全
  • 原文地址:https://www.cnblogs.com/starof/p/6517858.html
Copyright © 2020-2023  润新知