ECMA-262把對象定義爲:無序屬性的集合,其屬性能夠包含基本值,對象或者函數。因此js中對象就是一組鍵值對。javascript
面向對象的語言中,都是經過類的來建立任意多個具備相同屬性和方法的對象實例的。可是js中沒有類的概念,接下來我先經過一個例子來闡述js中沒有「類」的概念蘊含的哲學。這點會讓初學者很困惑,可是也正由於放下了「類」的概念,js對象纔有了其餘編程語言沒有的活力。事實上js中對象的「類」是從無到有,又不斷演化,最終消失於無形之中。html
舉例:小蝌蚪找媽媽的故事,小蝌蚪在其自身類型不斷演化的過程當中,逐漸變成了和媽媽同樣的「類」。java
代碼:程序員
<!DOCTYPE html> <html> <meta charset="utf-8"> <head> <title>小蝌蚪找媽媽</title> </head> <body> <script> var life={};//光溜溜的生命 for(life.age=1;life.age<=3;life.age++) { switch (life.age) { case 1: life.body="卵細胞";//增長body屬性 life.say=function(){ console.log(this.age+this.body); };//新建say方法 break; case 2: life.tail="尾巴";//增長tail屬性 life.gill="腮";//增長gail屬性 life.body="蝌蚪"; life.say=function(){ console.log(this.age+this.body+'-'+this.tail+","+this.gill); }; break; case 3: delete life.tail;//刪除tail屬性 delete life.gill;//刪除gill屬性 life.legs="四條腿";//增長legs屬性 life.lung="肺";//增長lung屬性 life.body="青蛙"; life.say=function(){ console.log(this.age+this.body+"-"+this.legs+","+this.lung); }; break; } life.say();//調用say方法,每次邏輯都會發生動態改變 } </script> </body> </html>
效果:編程
(1)js程序一開始產生了一個生命對象life,life誕生時只是個光溜溜的生命對象,沒有任何屬性和方法。設計模式
(2)第一次生命進化,life對象有了身體屬性body,並有了一個say方法,看起來是一個「卵細胞」。數組
(3)第二次生命進化,它又長出了「尾巴」和「腮」,有了tail和gill屬性,顯示它是一個「蝌蚪」。安全
(4)第三次生命進化,它的tail和gill消失了,但又長出了「四條腿」和「肺」,有了legs和lung屬性,從而最終變成了「青蛙」。編程語言
因此說,對象的「類」是從無到有,又不斷演化,最終消失於無形中。函數
「類」確實能夠幫助咱們對世界分類,可是咱們思想不能被「類」束縛,若是生命開始就被規定了固定的「類」,就沒法演化,蝌蚪就變不成青蛙。因此js中沒有「類」,類已化爲無形與對象融爲一體。這樣也更加貼近現實世界,不是嗎?
每一個對象都是基於一個引用類型建立的,這個引用類型能夠是原生類型也能夠是開發人員定義的類型。
js沒有類,js對象就是一組鍵值對,接下來看看js中9種建立對象的方式。
js建立對象的方法的產生是一個迭代的過程,由於已有方法的缺陷催生出新的方法。首先是早期js程序員常用也是最簡單的方法——經過Objec構造函數建立對象。
代碼:
<script> var person=new Object(); person.nam="lxy"; person.age="22"; person.job="Software Engineer"; person.sayName= function () { alert(this.nam); } person.sayName(); </script>
優勢:簡單
早期JS開發人員常用new Object()建立對象,幾年後對象字面量稱爲建立對象的首選模式。
代碼:
<script> var person={ name:"lxy", age:22, job:"Software Engineer", sayName:function(){ alert(this.name); } }; person.sayName(); </script>
要注意一點就是每聲明一個鍵值對後面標點是「,」。
這些屬性在建立時都帶有一些特徵值(characteristic),JavaScript經過這些特徵值來定義它們的行爲。
對象字面量相對於Object構造函數代碼量少了一點點。可是這2種方法經過一個接口建立不少對象,會產生大量重複代碼。Don't Repeat Yourself!咱們須要對重複的代碼進行抽象。工廠模式就是在這種狀況下出現的。
工廠模式是軟件工程領域一種廣爲人知的設計模式,這種模式抽象了建立具體對象的過程。
經過類來建立多個實例必然能夠減小代碼重複,可是ECMAScript中沒法建立類,因此就用函數來封裝以特定接口建立對象的細節。
代碼:
<script> function createPerson(name ,age,job){ var o=new Object(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name); } return o; } var lxy=createPerson("lxy",22,"Software Engineer"); var strangerA=createPerson("strangerA",24,"Doctor"); lxy.sayName(); strangerA.sayName(); </script>
工廠模式減小了重複代碼,可是不可以識別對象,全部實例都是object類型的。
這時構造函數模式就出現了。
像Object和Array這樣的原生構造函數,在運行時會自動出如今執行環境中。咱們能夠建立自定義構造函數,從而建立特定類型的對象。
代碼:
<script> function Person(name ,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name); } } var lxy=new Person("lxy",22,"Software Engineer"); var strangerA=new Person("strangerA",24,"Doctor"); lxy.sayName(); strangerA.sayName(); </script>
構造函數中首字母大寫,而非構造函數首字母小寫做爲區別。
經過new操做符來建立Person實例,這樣建立的實例都有一個constractor(構造函數)屬性,該屬性指向Person。
alert(lxy.constructor==Person);//true
alert(strangerA.constructor==Person);//true
lxy和strangeA是Person的實例,同時也是Object的實例。由於全部的對象都繼承自Object。
建立自定義構造函數意味着未來能夠將它的實例標識爲一種特定的類型;而這正是構造函數賽過工廠模式的地方。
構造函數也是函數,因此語法上能夠像普通函數同樣去用,可是能夠用並不表明應該用,仍是以構造函數的方式用更合理。
構造函數的問題是,同一構造函數的不一樣實例的相同方法是不同的。
alert(lxy.sayName==strangerA.sayName());//false
這個問題很好理解,由於js中函數就是對象,每定義一個函數,也就是實例化了一個對象。
從代碼的角度可能理解的更深入:
this.sayName=function(){alert(this.name)};與
this.sayName=new Function(alert(this.name));是等價的。
因此使用構造函數建立對象,每一個方法在每一個實例上都要從新實現一遍,一是耗資源,二是建立兩個或者多個完成一樣任務的Function沒有必要,三是有this在,不必在代碼執行前就把函數綁定到特定對象上。
因此,有一種方法是說把函數定義轉移到構造函數外部,代碼以下:
<script> function Person(name ,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=sayName; } function sayName(){ alert(this.name); } var lxy=new Person("lxy",22,"Software Engineer"); var strangerA=new Person("strangerA",24,"Doctor"); lxy.sayName(); strangerA.sayName(); </script>
把sayName()函數的定義轉移到構造函數外部,成爲全局的函數,構造函數內部把sayName賦爲爲全局的sayName。這樣sayName是一個指向外部函數的指針,所以lxy和strangeA就共享了 全局的sayName函數。
alert(lxy.sayName==strangerA.sayName);//true
可是這會有更糟糕的問題:全局做用域的函數只能被某個對象調用,這名存實亡啊,會形成對全局環境的污染;更糟糕的是構造函數有多少個方法,就要定義多少個全局函數, 那構造函數就絲毫沒有封裝性可言了。
可是這樣的想法是難得的,爲原型模式作了鋪墊,構造函數建立對象問題的解決辦法是原型模式。
原型模式就是把構造函數中方法拿出來的基礎上,爲了不對全局環境的污染,再作了一層封裝,可是畢竟是一種新的模式,它封裝的更完全,並且也不是把全部的函數都封裝,而是恰到好處的把構造函數中公共的方法和屬性進行了封裝。
代碼:
<script type="text/javascript"> function Person(){ } Person.prototype.name="lxy"; Person.prototype.age=22; Person.prototype.job="Software Engineer"; Person.prototype.sayName=function(){ alert(this.name); } var lxy=new Person(); lxy.sayName(); var personA=new Person(); personA.sayName(); alert(lxy.sayName()==personA.sayName());//true </script>
使用原型的好處是可讓全部的實例共享它所包含的屬性和方法。完美的解決了構造函數的問題。由於原型是js中的一個核心內容,其信息量很大,因此另做介紹,有興趣可看《javascript原型Prototype》。
原型也有它自己的問題,共享的屬性值若是是引用類型,一個實例對該屬性的修改會影響到其餘實例。這正是原型模式不多單獨被使用的緣由。
<script type="text/javascript"> function Person(){ } Person.prototype.name="lxy"; Person.prototype.age=22; Person.prototype.job="Software Engineer"; Person.prototype.friends=["firend1","friend2"]; Person.prototype.sayName=function(){ alert(this.name); } var lxy=new Person(); var personA=new Person(); alert(lxy.friends);//friend1,friend2 alert(personA.friends);//friend1,friend2 alert(lxy.friends==personA.friends);//true lxy.friends.push("friend3"); alert(lxy.friends);//friend1,friend2,friend3 alert(personA.friends);//friend1,friend2,friend3 alert(lxy.friends==personA.friends);//true </script>
建立自定義類型的最多見方式,就是組合使用構造函數模式和原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義共享的方法和屬性。結果,每一個實例都有一份實例屬性的副本,同時又共享着對方法的引用,最大限度的節省了內存。另外,這種混合模式還支持向構造函數傳遞參數,可謂是集兩種模式之長。
代碼:
<script type="text/javascript"> function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["firend1","friend2"]; } Person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } } var lxy=new Person("lxy",22,"Software Engineer"); var personA=new Person("personA",25,"Doctor"); alert(lxy.friends);//friend1,friend2 alert(personA.friends);//friend1,friend2 alert(lxy.friends==personA.friends);//false lxy.friends.push("friend3"); alert(lxy.friends);//friend1,friend2,friend3 alert(personA.friends);//friend1,friend2 </script>
實例屬性在構造函數中定義,而共享屬性constructor和共享方法sayName()在原型中定義。而修改一個實例的friends不會影響其餘實例的friends,由於它們引用不一樣數組,根本不要緊。
這種構造函數與原型混合模式,是目前使用最普遍、認同度最高的一種建立自定義類型的方法。能夠說,這是用來定義引用類型的一種默認模式。其實原型就是爲構造函數服務的,配合它來建立對象,想要只經過原型一勞永逸的建立對象是不可取的,由於它只管建立共享的屬性和方法,剩下的就交給構造函數來完成。
我的以爲構造函數和原型混合模式已經能夠完美的完成任務了。可是動態原型模式的提出是由於混合模式中用了構造函數對象竟然還沒建立成功,還須要再操做原型,這在其餘OO語言開發人員看來很彆扭。因此把全部信息都封裝到構造函數中,即經過構造函數必要時初始化原型,在構造函數中同時使用了構造函數和原型,這就成了動態原型模式。真正用的時候要經過檢查某個應該存在的方法是否有效,來決定是否須要初始化原型。
<script type="text/javascript"> function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["firend1","friend2"]; if(typeof this.sayName!="function"){ alert("初始化原型");//只執行一次 Person.prototype.sayName=function(){ alert(this.name); } } } var lxy=new Person("lxy",22,"Software Engineer"); lxy.sayName(); var personA=new Person("personA",25,"Doctor"); personA.sayName(); </script>
使用動態原型時,不能使用對象字面量重寫原型。若是在已經建立了實例的狀況下重寫原型,那麼就會切斷現有實例與新原型之間的聯繫。
在前面幾種模式都不適用的狀況下,適用寄生(parasitic)構造函數模式。寄生模式其實就是把工廠模式封裝在構造函數模式裏,即建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象,從表面看,這個函數又很像典型的構造函數。
代碼:
<script type="text/javascript"> function Person(name,age,job){ var o=new Object(); o.name=name; o.age=age; o.sayName=function(){ alert(this.name); } return o; } var lxy=new Person("lxy",22,"Software Engineer"); lxy.sayName(); </script>
除了適用new操做符使得該函數成爲構造函數外,這個模式和工廠模式如出一轍。
返回的對象和構造函數或者構造的原型屬性之間沒有任何關係,因此不能用instanceof,這種方式建立的對象和在構造函數外面建立的對象沒什麼兩樣。
穩妥的構造函數模式用顯式聲明的方法來訪問屬性。
和這種模式相關的有一個概念:穩妥對象。穩妥對象,指沒有公共對象,並且其方法也不引用this的對象。
穩妥對象最適合在一些安全的環境中(這些環境中會禁用this和new),或者防止數據被其餘應用程序(如Mashup)改動時使用。
穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:一是新建立對象的實例方法不引用this,而是不使用new操做符調用構造函數。
代碼:
<script type="text/javascript"> function Person(name,age,job){ //建立要返回的對象 var o=new Object(); //能夠在這裏定義私有變量和函數 //添加方法 o.sayName=function(){ alert(name); } return o; } var lxy=new Person("lxy",22,"Software Engineer"); lxy.sayName(); alert(lxy.name);//undefined alert(lxy.age);//undefined </script>
以這種模式建立的對象中,lxy是一個穩妥對象,除了使用sayName()方法外,沒有其餘方法訪問name的值,age,job相似。
即便有其餘代碼會給這個對象添加方法或數據成員,但也不可能有別的方法訪問傳入構造函數中的原始數據。
與寄生構造函數模式相似,使用穩妥構造函數模式建立的對象與構造函數之間也沒有什麼關係,所以不能用instanceof操做符。
各類建立方法問題總結:
工廠模式:無法知道一對象的類型。
構造函數:多個實例之間共享方法。
原型:屬性值是引用類型時,一個實例對該屬性的修改會影響到其餘實例。
組合使用構造函數模式和原型模式:【推薦】構造函數模式用於定義實例屬性,每一個實例都有一份實例屬性的副本;而原型模式用於定義共享的方法和屬性,每一個實例同時又共享着對方法的引用
本文做者starof,因知識自己在變化,做者也在不斷學習成長,文章內容也不定時更新,爲避免誤導讀者,方便追根溯源,請諸位轉載註明出處:http://www.cnblogs.com/starof/p/4904929.html有問題歡迎與我討論,共同進步。