javascript面向對象

# javascript面向對象(一)  

對象其實就是一種引用類型。而對象的值就是引用類型的實例。在JavaScript 中引用類型是一種數據結構,將數據和功能組織在一塊兒。它也常被稱作爲類,但JavaScript 中卻沒有類的概念。雖然JavaScript 是一門面向對象的語言,卻不具有傳統面嚮對象語言所支持的類和接口等基本結構。  

## 對象的建立以及經常使用操做 ##

1. **使用new運算符**
javascript

var user = new Object();        //使用new運算符建立一個對象

        user.name = 'tina;        //給對象添加屬性

        user.age = 22;

        user.address = '四川成都';  


2. **對象字面量(JSON方式)**  
java

var user = {

            name:'編程浪子',

            age:22,

            address:'四川成都'    

        };  


3. **簡單方式(傳統賦值方式)**  
程序員

 var user = {};

        user.name = 'tina';        //給對象添加屬性

        user.age = 22;

        user.address = '四川成都';


4. **屬性的調用**  

    對於對象屬性的調用有兩種方式:
    調用方法以下:
    alert(user.name + " " +user.age);//返回 '編程浪子 四川成都'  
    另外一種方法: 
    alert(user['name'] + " " +user['age']);//返回 '編程浪子 四川成都'    

5. **添加方法**  
    
編程

var user = {
            name:'tina',        //給對象添加屬性
            age:22,
            address:'四川成都',
            showInfo:function(){//添加一個方法
                alert(this.name+" "+this.age+" "+this.address);
            },
            showHello:showHello//將對象外部的方法添加到對象
        };
        function showHello(){
            alert("Hello!");    
        }
        user.showInfo();//調用方法
        user.showHello();  

 

# javascript面向對象(二) #

## 建立對象 ##

咱們知道,要建立一個對象咱們能夠用以下代碼:  

數組

  var user = new Object();  

    user.name = '編程浪子';

    user.age = 22;

    user.address = '四川成都';  


  用這樣的方法建立對象比較簡單直觀,也是JavaScript種建立對象最基本的方法。可是這樣就有一個問題,若是咱們須要建立多個對象,那麼我就得寫不少重複的代碼。好比咱們想建立另外一個對象user1,咱們就得從新將上面的代碼從新寫一遍,這在實際開發過程當中是不合適的,這樣若是對象過多,代碼量將大大增長。



爲了解決這樣的問題,咱們可使用一種叫作**工廠模式**的方法,這種方法 就是爲了解決實例化對象產生大量重複代碼的問題。  

## 工廠模式 ##
瀏覽器

 1  function create(name, age) {
 2 
 3       var obj = new Object(); 
 4 
 5       obj.name = name; 
 6 
 7       obj.age = age;
 8 
 9       obj.show = function () {
10 
11         return this.name +' '+ this.age;
12 
13       };
14 
15       return obj;
16 
17     }
18 
19     var obj1= create('bclz', 30);    //第一個實例
20 
21     var obj2= create('bcxb', 20);    //第二個實例
22 
23     alert(obj1.show());
24 
25     alert(obj2.show());  


   
從上面的代碼咱們能夠看出,工廠模式解決了實例化時代碼大量重複的問題,但又出現了一個問題,那就是識別問題,咱們根本沒法弄清楚他們究竟是哪一個對象的實例。好比
數據結構

alert(typeof obj1);  //Object
    alert(obj1 instanceof Object);  //true


以上代碼標明obj1是Object對象,可是咱們沒法知道具體是哪個對象建立的。  

## 構造函數(構造方法) ##
函數

 1 function User(name, age) {    //構造函數模式
 2 
 3       this.name = name;
 4 
 5       this.age = age;
 6 
 7       this.show = function () {
 8 
 9         return this.name  + ' '+this.age;
10 
11       };
12 
13     }  


    建立對象的時候用new運算符就能夠了:
測試

var user1 = new User('bclz', 30);    //第一個實例

    var user2 = new User('bcxb', 20);    //第二個實例



如今咱們就能夠檢測user1或者user2是否是屬於User。

this

alert(user1 instanceof User);//true



可見,使用構造函數的方法,即解決了**重複實例化**的問題,又解決了**對象識別**的問題。


要建立User對象的新實例,就要使用new操做符,使用這個方式構建實例對象,會通過下面4個步驟:

1.建立一個新對象;
2.將構造函數的做用域給新對象(所以this指向的這個新對象)。
3.執行構造函數內的代碼在(爲新對象添加屬性);
4.返回新對象。

不過須要注意下面兩個問題:  

1):構造函數也是函數**  

構造函數與函數的惟一區別,就是調用方式的不一樣,不過,構造函數畢竟也是函數,不存在什麼特殊的定義構造函數的語法。任何函數,只要經過new操做符來調用,就能夠把它看做是構造函數;而任何函數,若是不經過new操做符調用,它就和普通函數沒有什麼區別,例如前面定義的User:  

    

 1 //看成構造函數調用
 2 
 3     var user1 = new User('bclz', 30);  
 4 
 5     user1.show(); //bclz 30;
 6 
 7 
 8 
 9     //看成普通函數調用
10 
11     User('bclz', 30);
12 
13     window.show(); //bclz 30;  


結果上沒有什麼區別,只是你們能夠看到,看成普通函數調用的話,函數裏this對象的指向,實際上是指向的window全局對象。而經過new關鍵字調用,this指向的則是新對象而已,因此,其實還能夠這麼來寫:

  

    var o = new Object();

    User.call(o,'bclz', 30);

    o.show();  





經過函數方法call來從新定義對象的做用域,這裏很少作解釋,講到函數細節時再仔細介紹這種方法,這裏只是說明能夠改變對象的做用域的,其實就是**改變this的指向**  


**2):構造函數的問題**



構造函數的模式雖然好,可是並不是沒有缺點。構造函數最大的問題就是,每一個方法都要在實例上從新建立一次。在前面的例子中,user1和user2中都有一個show方法,若是咱們執行如下語句:

 alert(user1.show==user2.show);//結果返回的是false

 

結果返回的是false,這就說明方法其實也是一種引用地址。若是咱們一樣重複建立了多個對象,那麼每一個對象中的方法都會在內存中開闢新的空間,這樣浪費的空間就比較多。要解決這個問題,咱們就須要用到實例屬性或者方法的共享。  咱們可使用一種變通的方式,來達到咱們想要的效果,也就是讓show方法再也不重複建立  

 1  function User(name, age) {    
 2 
 3       this.name = name;
 4 
 5       this.age = age;
 6 
 7       this.show = show;
 8 
 9     } 
10 
11     function show(){
12 
13         alert(this.name + ' ' + this.age);
14 
15     }  



將show方法移到外部,至關於show方法成了一個全局函數,而後再到User構造函數內部去引用show方法,這樣User內部的this.show都指向了同一個全局函數show,所以,咱們實例化的user1和user2就實現了共享,能夠再次調用:

 alert(user1.show==user2.show);//結果返回的是true  

  
可是這只是一個測試,若是你要讓更多的屬性或者方法實現共享,那不是要定義更多的全局函數或者變量,這種方式是不科學也不可行的。所以,咱們須要引入另一個javascript面向對象的重要概念**原型**

 

# javascript面向對象(三) #

## Prototype原型模式 ##




## 經過構造函數的弊端引出原型概念 ##

爲了講清楚原型,咱們仍是必須先回顧一下構造函數的問題,用一個簡單的例子再次講解一下,咱們有一隻貓的構造函數,以下:  

1   function Cat(name,color){
2         this.name = name;
3         this.color = color;
4     }


  
這個構造函數很簡單,再也不多說,那麼如今再爲這個函數添加一個不變的屬性"type"(種類),再添加一個方法eat(吃老鼠)。那麼,Cat就變成了下面這樣:  

 

 1     function Cat(name,color){
 2         this.name = name;
 3         this.color = color;
 4         this.type = "貓科動物";
 5         this.eat = function(){alert("吃老鼠");};
 6     }
 7 
 8 
 9 //生成實例:  
10 
11     var cat1 = new Cat("大毛","黃色");
12     var cat2 = new Cat ("二毛","黑色");
13     alert(cat1.type); // 貓科動物
14     cat1.eat(); // 吃老鼠  


表面上好像沒什麼問題,可是實際上這樣作,有一個很大的弊端。那就是對於每個實例對象,type屬性和eat()方法都是如出一轍的內容,每一次生成一個實例,都必須爲重複的內容,多佔用一些內存。這樣既不環保,也缺少效率。  

   alert(cat1.eat == cat2.eat); //false 



所以,爲了讓type屬性和eat()方法在內存中**只生成一次**,而後全部實例都指向那個內存地址,引出了原型




## 什麼是原型? ##



> 原型對象實際上就是構造函數的一個實例對象,和普通的實例對象沒有本質上的區別。能夠包含特定類型的全部實例的共享屬性或者方法。 這個prototype的屬性值是一個對象(屬性的集合),默認的只有一個叫作constructor的屬性,指向這個函數自己。  
好比咱們簡單定義一個SuperType名字的函數,裏面什麼屬性也沒有在函數內部是這個樣子的   
  

  function SuperType(){

    }  





從上圖咱們看到,函數裏面雖然什麼都沒有,可是有一個默認的prototype屬性,它是一個對象,它指向的是本身的地址,而prototype這個對象自己裏面又有一個屬性constructor,而這個屬性,又指向了函數自己,有點繞,你能夠經過下面的代碼作一下測試,看看效果  

    alert(SuperType.prototype) //object

    alert(SuperType.prototype.constructor) //彈出函數自己function SuperType(){} 

 




  

prototype和constructor是原型最基本的概念,如今看可能還有點暈,不要緊,我直接上之前的代碼,看看區別,仍是以前的Cat構造函數,將它修改一下:  

 1  function Cat(name,color){
 2 
 3         this.name = name;
 4 
 5         this.color = color;
 6 
 7     }
 8 
 9     Cat.prototype.type = "貓科動物";
10 
11     Cat.prototype.eat = function(){alert("吃老鼠")};  
12 
13 //生成實例:
14 
15 
16 
17     var cat1 = new Cat("大毛","黃色");
18 
19     var cat2 = new Cat("二毛","黑色");
20 
21     alert(cat1.type); // 貓科動物
22 
23     cat1.eat(); // 吃老鼠  


這時全部實例的type屬性和eat()方法,其實都是**同一個內存地址**,**指向prototype對象**,所以就提升了運行效率。 

alert(cat1.eat == cat2.eat); //true


## Prototype模式的驗證方法 ##

爲了配合prototype屬性,Javascript定義了一些輔助方法,幫助咱們使用它。  

**isPrototypeOf()**  

這個方法用來判斷,某個proptotype對象和某個實例之間的關係。   

    alert(Cat.prototype.isPrototypeOf(cat1)); //true

    alert(Cat.prototype.isPrototypeOf(cat2)); //true   

 



**hasOwnProperty()**  

每一個實例對象都有一個hasOwnProperty()方法,用來判斷某一個屬性究竟是本地屬性,仍是繼承自prototype對象的屬性。

    alert(cat1.hasOwnProperty("name")); // true

    alert(cat1.hasOwnProperty("type")); // false  

 


**in運算符**  

in運算符能夠用來判斷,某個實例是否含有某個屬性,無論是否是本地屬性。  

    alert("name" in cat1); // true

    alert("type" in cat1); // true  

 


in運算符還能夠用來遍歷某個對象的全部屬性。  

  for(var prop in cat1) { 

        alert("cat1["+prop+"]="+cat1[prop]); 

    }

 




來看一下 **javascript高級程序設計** 書中對與原型的描述和說明  

 1   function Person(){ }  //建立Person構造函數
 2 
 3     Person.prototype.name = "Nicholas";//建立共享屬性name 
 4 
 5     Person.prototype.age = 29; //建立共享屬性age 
 6 
 7     Person.prototype.job = "Software Engineer"; //建立共享屬性job 
 8 
 9     Person.prototype.sayName = function(){     //建立共享函數sayName
10 
11         alert(this.name); 
12 
13     };  
14 
15     
16 
17     //分別建立了person1和person2,裏面都有sayName函數,而且彈出的值都是同樣
18 
19     var person1 = new Person(); 
20 
21     person1.sayName();   //"Nicholas"  
22 
23     var person2 = new Person();
24 
25     person2.sayName();   //"Nicholas"  
26 
27     alert(person1.sayName == person2.sayName);  //true   



經過上面的圖,能夠看到,person1和person2,他們內部都有一個指向Person.prototype的指針,能夠經過原型的isPrototype方法測試一下 

 1  alert(Person.prototype.isPrototypeOf(person1));  //true
 2 
 3     alert(Person.prototype.isPrototypeOf(person2));  //true
 4 
 5     function User(){};
 6 
 7     var person3 = new User();
 8 
 9     alert(Person.prototype.isPrototypeOf(person3));  //false
10 
11     alert(User.prototype.isPrototypeOf(person3));  //true 

 




## 對象的\__proto\__隱式原型 ##

上面咱們建立了兩個對象,person1和person2,這兩個對象,也都指向了Person構造函數的原型,這是由於每一個對象都有一個隱藏的屬性——「\__proto\__」,這個屬性引用了建立這個對象的函數的prototype。即:person1.\__proto\__ === Person.prototype  

這個\__proto\__是一個隱藏的屬性,javascript不但願開發者用到這個屬性值,有的低版本瀏覽器甚至不支持這個屬性值。看下面的代碼:  

    console.log(Object.prototype);

    var obj = new Object();

    console.log(obj.__proto__);


你會發現打印了相同的內容:
  



obj這個對象本質上是被Object函數建立的,所以obj.\__proto\__=== Object.prototype。咱們能夠用一個圖來表示。   



關於隱式原型,主要涉及到原型繼承的主要原理,這裏只是拋出這個概念稍做介紹

# javascript面向對象(四) #

## Prototype原型模式 ##

上一章羅列一直知識點,可是主要是爲了說明prototype原型,如今主要來看看,經過原型來建立對象的幾種方式  



### 基本原型 ###

 1  function Person(){ }  
 2 
 3     Person.prototype.name = "Nicholas"; 
 4 
 5     Person.prototype.age = 29; 
 6 
 7     Person.prototype.job = "Software Engineer"; 
 8 
 9     Person.prototype.sayName = function(){     
10 
11         alert(this.name); 
12 
13     };  

 

  
固然這種方式只是說明原型的道理,實際使用中不多把屬性寫在原型中  



### 更簡單的方式 ###

 1 function Person(){ } 
 2     Person.prototype = {     
 3 
 4         name : "Nicholas",     
 5 
 6         age : 29,     
 7 
 8         job: "Software Engineer",     
 9 
10         sayName : function () {         
11 
12             alert(this.name);     
13 
14         } 
15 
16     }; 



這種方式只是上面方式的簡單寫法,經過對象字面量直接寫完全部屬性。效果和上面的寫法是同樣的,只是寫法不同。  

可是直接所有把屬性和方法所有寫在原型中,這並不現實,看下面的列子:  

 1 function Person(){ }
 2 
 3   
 4 
 5     Person.prototype = {     
 6 
 7         constructor: Person,     
 8 
 9         name : "Nicholas",     
10 
11         age : 29,     
12 
13         job : "Software Engineer",     
14 
15         friends : ["Shelby", "Court"],     
16 
17         sayName : function () {         
18 
19             alert(this.name);     
20 
21         } 
22 
23     }; 
24 
25  
26 
27     var person1 = new Person(); 
28 
29     var person2 = new Person();  
30 
31     person1.friends.push("Van");  
32 
33     alert(person1.friends);    //"Shelby,Court,Van" 
34 
35     alert(person2.friends);    //"Shelby,Court,Van" 
36 
37     alert(person1.friends === person2.friends);  //true  



上面的列子很容易看出,講屬性寫在原型中的問題,列子中的friends是個數組,引用數據類型在person1中修改了friends,添加了一個條數據以後,能夠看到person1和person2對象的friends都發生了改變,這其實很好理解,由於prototype對象自己就是共享的,數組又是屬於引用類型,改變了一個,其餘的都會發生改變。

因此,在實際中使用的更多的方法是構造函數與原型結合的方式  

### 構造函數與原型結合的方式 ###

 1  function Person(name, age, job){     
 2 
 3         this.name = name;     
 4 
 5         this.age = age;     
 6 
 7         this.job = job;     
 8 
 9         this.friends = ["Shelby", "Court"]; 
10 
11     }  
12 
13     Person.prototype = {     
14 
15         constructor : Person,     
16 
17         sayName : function(){         
18 
19             alert(this.name);     
20 
21         } 
22 
23     } 
24 
25 
26 
27     var person1 = new Person("Nicholas", 29, "Software Engineer"); 
28 
29     var person2 = new Person("Greg", 27, "Doctor");  
30 
31     person1.friends.push("Van"); 
32 
33     alert(person1.friends);    //"Shelby,Count,Van" 
34 
35     alert(person2.friends);    //"Shelby,Count" 
36 
37     alert(person1.friends === person2.friends);    //false 
38 
39     alert(person1.sayName === person2.sayName);    //true   


這裏就能夠看到,friends的屬性在兩個對象中就算改變了其中一個,並不會對另一個產生影響。這種構造函數加原型的混成模式,是目前使用率,承認率最高的一種自定義類型的方式,因此,通常狀況下,咱們定義自定義類型默認都使用這種模式  



### 動態原型模式 ###

這種模式只是上面模式的變種,對於一些習慣書寫面嚮對象語言的程序員來講,一個類要分開兩個部分來寫,是很是不習慣的,因此,就有了動態原型模式,其實無非就是,把以前分開兩部分寫的內容,所有提到函數中,加上判斷就好了

 1  function Person(name, age, job){  
 2 
 3 
 4 
 5         //屬性  
 6 
 7         this.name = name;     
 8 
 9         this.age = age;     
10 
11         this.job = job; 
12 
13  
14 
15         //方法  
16 
17         if (typeof this.sayName != "function"){              
18 
19             Person.prototype.sayName = function(){             
20 
21                 alert(this.name);         
22 
23             };              
24 
25         } 
26 
27     }  
28 
29 
30 
31     var friend = new Person("Nicholas", 29, "Software Engineer"); 
32 
33     friend.sayName();  

注意上面的判斷,這種方式只有在sayName函數不存在的狀況下,纔會將它添加到原型中,若是sayName函數已經存在,那麼這段代碼就不會再運行,並且就算有不少方法的話,if語句也不用所有判斷,只是須要判斷一個就好了。  這樣的寫法,對於java或者C#程序員相對來講感官上比較容易接受,並且寫法也沒有任何缺陷。可是,有一點不算是缺陷的缺點,javascript是一門動態語言,也就是說,屬性和方法是隨時能夠添加的,若是所有寫在構造函數裏面去,反而看起來不是那麼的靈活。因此,通常狀況下,使用構造函數與原型的混合模式的仍是比較多的

相關文章
相關標籤/搜索