JavsScript中對象繼承關係變得可有可無,對於一個對象來講重要的是它能作什麼,而不是它從哪裏來。javascript
JavaScript提供了一套更爲豐富的代碼重用模式。它能夠模擬那些基於類的模式,同時它也能夠支持其餘更具表現力的模式。html
JavaScript是一門基於原型的語言,這意味着對象直接從其餘對象繼承。java
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; });
定義一構造器並擴充它的原型: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。
構造器要接受一大串參數,要記住參數的順序很是困難。因此編寫構造器時讓它接受一個簡單的對象說明符,更友好。
//接受一大串參數 var myObject=maker(f,l,m,c,s); //對象字面量更友好 var myObject=maker({ first:f, middle:m, last:l, state:s, city:c });
對象字面量好處:
原型模式中,摒棄類,轉而專一於對象。概念:一個新對象能夠繼承一箇舊對象的屬性。 經過構造一個有用的對象開始,接着能夠構造更多和那個對象相似的對象。這就能夠徹底避免把一個應用拆解成一系列嵌套抽象類的分類過程。
用對象字面量構造一個有用的對象。
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; }
至此,上面咱們看到的繼承模式的一個弱點就是:無法包含隱私。對象的全部屬性都是可見的。
應用模塊模式,能夠解決這個問題。
從構造一個生成對象的函數開始。咱們以小寫字母開頭來命名它,由於它並不須要使用new前綴。該函數包括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>
咱們能夠從一套部件中把對象組裝出來。
例如,咱們能夠構造一個給任何對象添加簡單事件處理特性的函數。它會給對象添加一個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有問題歡迎與我討論,共同進步。