JavaScript構造器理解

8gw234 9年前發布 | 15K 次閱讀 JavaScript開發 JavaScript

類 Class 類的概念應該是面向對象語言的一個特色,但是JavaScript并不像Java,C++等高級語言那樣擁有正式的類,而是多數通過構造器以及原型方式來仿造實現。在討論構造器和原型方法前,我可以看看一種叫做工廠方式的仿造方法。

工廠模式

<script>
    function material() { //工廠模式
        alert('steel');
    }

function createCar(name, color) {
    var car      = {};
    car.name     = name;
    car.color    = color;
    car.material = material;

    return car; //類似調用函數,必須要有返回值
}

var car1 = createCar('BMW', '白色');
var car2 = createCar('Benz', '黑色');

console.log(car1);
console.log(car2);

</script></pre>

JavaScript構造器理解
這種方式顯然可以實現class的功能,但是外形上怎么也無法說它是個class以及class實例的創建過程。因此,出現了“構造函數方式”,它的關鍵在于構造器(Constructor)概念的引入。


構造函數方式

構造器(Constructor)

<script>
    function material() { //公共部分
        alert('stell');
    }

//constructor 實例化時,此函數將會作為實例化后對象的構造函數
//**構造函數名首字母最好大寫,以區分和普通函數**
function CreateCar(name, color) {
    this.name = name;
    this.color = color;
    this.material = material;
}

var car1 = new CreateCar('BMW', '白色');
var car2 = new CreateCar('BenZ', '黑色');

console.log(car1);
console.log(car2);
console.log("/n");

</script></pre>

JavaScript構造器理解

這個看起來有點類的樣子了吧(先不提那個難看的外置function)?我們發現,那個constructor其實就是一個簡單的function,它與“工廠方式”中的createCar()區別就在于:
1、方法名大寫
2、沒有了空對象的創建和返回
3、使用this做引用。
那原來的那個空對象的創建以及返回的步驟去哪了呢?這兩個步驟,現在都由創建實例時的“new”實現了。“new”這個操作符負責創建一個空對象,然后將那個叫做構造器的function添加到實例對象中并觸發它,這樣這個function實際上就是這個對象的一個method,function中的this指向的便是這個對象,最后將這個對象返回。根據如上分析,我們可以把這個過程簡單分解為如下代碼:

<script>
    var obj = {};
    obj.constructor = CreateCar;
    obj.constructor("BMW", "白色");  //”this“ refers to obj
    return obj;
</script>

構造函數方式雖然與高級面向對象語言中的類創建方式已經很接近(使用new創建),但是貌似那個游離在類之外的function material()其實卻是個相當有礙觀瞻的瑕疵。我們應該想一種辦法讓這個方法與類掛鉤,讓它成為類的一個屬性,不是全局的。于是,這就產生了“構造函數+原型法”的類構造方法。

構造函數 + 原型

從上面的構造函數模式創建對象的例子上可以看到,每創建一個對象實例,每個對象中都有material()這個成員方法,這樣看起來是不是會浪費內存空間,降低執行效率。所以JavaScript中提供了原型的方法可以解決這個問題。

原型:proto
在JavaScript中每個函數都有一個原型屬性,即prototype,當調用構造函數進行創建對象的時候,所有該構造函數原型的屬性在創建的對象上都可用。按照這樣的想法多個CreateCar都可以共享一個原型material.

構造函數+原型法中,我們對于類的method期待得到的效果是:
1. 僅是類的method而不是全局的。

  1. 只在類被定義時創建一個method實例,然后被所有類的實例共用。</strong>
    由這兩個目標,我們很容易想到高級面向對象語言Java的private static變量的特點。JavaScript沒有為我們提供這么簡單的符號來實現這個復雜功能,但是卻有一個屬性可以幫我們仿造出這種效果:prototype。我們先來看幾段prototype的使用代碼。 </p>

    > function Car(){ }
    > 
    > Car.prototype.material = "steel";
    > 
    > var car1 = new Car(); var car2 = new Car();
    > 
    > document.write(car1.material);  //prints "steel" 
    > document.write(car2.material);  //prints "steel" 
    > 
    > //car1.prototype.material = "iron" //compile error:car1.prototype is
    > undefined car1.material = "iron";
    > 
    > document.write(car1.material);  //prints "iron"
    > document.write(car2.material);  //prints "steel" 
    > document.write(Car.prototype.material); //prints "steel"
    > 
    > Car.prototype.material = "wood"; var car3 = new Car();
    > document.write(car1.material);  //prints "iron"
    > document.write(car2.material ); //prints "wood"
    > document.write(car3.material ); //prints "wood"
    > document.write(Car.prototype.material); //prints "wood"

     分析該段代碼前,需要明確兩個概念:對象的直屬屬性和繼承屬性。直接在構造函數中通過this.someproperty = xxx這種形式定義的someproperty屬性叫做對象的直屬屬性,而通過如上第4行代碼那樣Car.prototype.material = "steel";這種形式定義的material屬性叫做繼承屬性。由上面這段代碼,我們可以總結出prototype屬性的如下特點:
    

    </p>

       <li>
           prototype是function下的屬性(其實任意object都擁有該屬性,function是對象的一種)
       </li>
       <li>
           prototype屬性的值是一個對象,因此可任意添加子屬性(line 4)
       </li>
       <li>
           類的實例可以直接通過”.”來直接獲取prototype下的任意子屬性(line 9)
       </li>
       <li>
           所有以此function作為構造函數創建的類實例共用prototype中的屬性及值(ling 9,10)
       </li>
       <li>
           類的實例沒有prototype屬性(line 12)
       </li>
       <li>
           可以直接通過 “實例.屬性 = xxx” 的方式修改繼承屬性,修改后的值將覆蓋繼承自prototype的屬性,但此修改不影響prototype本身,也不影響其它類實例(line 15,16,17)
       </li>
       <li>
           繼承屬性修改后,該屬性就成為類實例的直屬屬性
       </li>
       <li>
           可以直接修改prototype的屬性值,此改變將作用于此類下的所有實例,但無法改變直屬屬性值(極晚綁定line 21-24)
       </li>
      

      </ol>

       <p>
           PS:對象實例在讀取某屬性時,如果在本身的直屬屬性中沒有查找到該屬性,那么就會去查找function下的prototype的屬性。
       </p>
      

      </blockquote>

      Tip:我們可以通過hasOwnProperty方法來判斷某屬性是直屬于對象還是繼承自它的prototype屬性
       car1.hasOwnProperty("material"); // true
       car2.hasOwnProperty("material"); // false
       "material" in car2;// true

       <strong>構造函數+原型方式代碼如下</strong> 
      

      </p>

      <script>
       function CreateCar(name, color) {

       this.name = name;
       this.color = color;
      

      }

      CreateCar.prototype.material = function() {

       //綁定material到函數原型鏈上,當實例化時這個函數會作為每個對象的構造函數
       alert(this.name);
      

      }

      var car1 = new CreateCar('BMW', '白色'); var car2 = new CreateCar('Benz', '黑色');

      console.log(car1); console.log(car2); console.log(car1.hasOwnProperty("material"));//false 因為material是原型鏈上的,不是對象本身的,所以false </script></pre>


       這個跟高級面向對象語言中的class的樣子更~加類似了吧?上述寫法只是在“語義”上達到了對類屬性和方法的封裝,很多面向對象思想的完美主義者希望在“視覺”上也達到封裝,因此就產生了“動態原型法”,請看下面的代碼:
      

      </p>

      function Car(color, title){
      this.color = color;
      this.title = title;
      if (typeof Car._initialized == "undefined") {
         Car.prototype.start = function(){

         alert("Bang!!!");
      

      }; Car.prototype.material = "steel"; Car._initialized = true; } }</pre>

       我們看,其實Car.prototype的屬性定義是可以被放進Car function的定義之中的,這樣就達到了“視覺”封裝。但是我們沒有單純的move,我們需要加一個條件,讓這些賦值操作只執行一次,而不是每次創建對象實例的時候都執行,造成內存空間的浪費。添加_initialized屬性的目的就在于此。
      

      </p>


       <p>
           對于所有用字面量創建的對象而言,其prototype對象均為Object.prototype(作為一個特殊對象,Object.prototype沒有原型對象):
       </p>
      

      </blockquote>

      var x = {a:18, b:28};

    console.log(x.proto);//Object {}</pre>

    而對于所有用new操作符創建的對象而言,其prototype對象均為constructor函數的prototype屬性:

    var x = {a:18, b:28};

    function Test(c){

    this.c = c;

    }

    Test.prototype = x;

    var t = new Test(38);

    console.log(t);//Object {c=38, a=18, b=28}

    console.log(t.proto);//Object {a=18, b=28}

    console.log(t.proto.proto);//Object {}</pre>


    總結出來一句話就是:用構造函數方式定義對象的所有非函數屬性,用原型方式定義對象的函數屬性。


    JavaScript構造器理解
    整理于:
    http://dbear.iteye.com/blog/613745
    http://www.cnblogs.com/tomxu/archive/2012/02/21/2352994.html

    </div> 來自:http://hao.jser.com/archive/7704/

 本文由用戶 8gw234 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!