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

posted @ 2017-03-09 17:52  starof  阅读(2511)  评论(0编辑  收藏  举报