zoukankan      html  css  js  c++  java
  • 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有问题欢迎与我讨论,共同进步。

  • 相关阅读:
    HDOJ 4747 Mex
    HDU 1203 I NEED A OFFER!
    HDU 2616 Kill the monster
    HDU 3496 Watch The Movie
    Codeforces 347A A. Difference Row
    Codeforces 347B B. Fixed Points
    Codeforces 372B B. Hungry Sequence
    HDU 1476 Sudoku Killer
    HDU 1987 How many ways
    HDU 2564 词组缩写
  • 原文地址:https://www.cnblogs.com/starof/p/6517858.html
Copyright © 2011-2022 走看看