javascript继承

JavsScript中对象继承关系变得可有可无,对于一个对象来讲重要的是它能作什么,而不是它从哪里来。javascript

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

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

1、伪类

一、原理

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

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

this.prototype={constructor:this}

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

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

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

二、伪类,即便用new前缀

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

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操做细节,可是问题还在:有了像“类” 的构造器函数,但仔细看它们,你会惊讶地发现:

一、没有私有环境,全部的属性都是公开的。

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

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

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

2、对象说明符

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

//接受一大串参数
var myObject=maker(f,l,m,c,s);

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

对象字面量好处:

  • 多个参数能够按任何顺序排列
  • 构造器若是聪明的使用了默认值,一些参数能够忽略掉
  • 和JSON一块儿用时,能够把JSON对象传给构造器,返回一个构造彻底的对象

3、原型

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

一、差别化继承

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

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)”,经过定制一新的对象,咱们指明它与所基于的基本对象的区别。

二、差别化继承优点

差别化继承,对某些数据结构继承于其余数据结构的情形很是有用。

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

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

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

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

4、函数化

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

应用模块模式,能够解决这个问题。

一、模块模式

从构造一个生成对象的函数开始。咱们以小写字母开头来命名它,由于它并不须要使用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不受该实例被修改的影响。*/

二、应用

咱们把这个模式应用到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

5、部件(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有问题欢迎与我讨论,共同进步。

相关文章
相关标签/搜索