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有問題歡迎與我討論,共同進步。

相關文章
相關標籤/搜索