面向對象的程序設計

一、理解對象

  • 定義對象編程

    • 建立一個 Object 的實例數組

      var person = new Object();
      person.name = "Nicholas";
      person.age = 29;
      person.job = "Software Engineer";
      person.sayName = function(){
          alert(this.name);//"Nicholas"
      };
    • 使用對象字面量語法瀏覽器

      var person = {
          name: "Nicholas",
          age: 29,
          job: "Software Engineer",
          sayName: function(){
              alert(this.name);
          }
      };
  • 屬性類型安全

    • 數據屬性:包含一個數據值的位置,在這個位置能夠讀取和寫入值app

      • [[Configurable]]:表示可否經過 delete 刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成訪問器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值爲 true函數

      • [[Enumerable]]:表示可否經過 for-in 循環返回屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值爲 true。測試

      • [[Writable]]:表示可否修改屬性的值。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值爲 trueui

      • [[Value]]:包含這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。這個特性的默認值爲 undefinedthis

      • 使用Object.defineProperty()方法:接收三個參數:屬性所在的對象、屬性的名字和一個描述符對象;其中,描述符(descriptor)對象的屬性必須是:configurable、enumerable、writable 和 valuegoogle

        var person = {};
        Object.defineProperty(person, "name", {
          writable: false,
          value: "Nicholas"
        });
        alert(person.name); //"Nicholas"
        person.name = "Greg";
        alert(person.name); //"Nicholas"
    • 訪問器屬性

      • [[Configurable]]:表示可否經過 delete 刪除屬性從而從新定義屬性,可否修改屬性的特性,或者可否把屬性修改成數據屬性。對於直接在對象上定義的屬性,這個特性的默認值爲true

      • [[Enumerable]]:表示可否經過 for-in 循環返回屬性。對於直接在對象上定義的屬性,這個特性的默認值爲 true

      • [[Get]]:在讀取屬性時調用的函數。默認值爲 undefined

      • [[Set]]:在寫入屬性時調用的函數。默認值爲 undefined

      • 使用Object.defineProperty()方法操做

        var book = {
          _year: 2004,
          edition: 1
        };
        Object.defineProperty(book, "year", {
          get: function(){
              return this._year;
          },
          set: function(newValue){
              if (newValue > 2004) {
                  this._year = newValue;
                  this.edition += newValue - 2004;
              }
          }
        });
        book.year = 2005;
        alert(book.edition); //2
      • 在不支持 Object.defineProperty() 方法的瀏覽器中使用兩個非標準的方法defineGetter__()__defineSetter__()操做,可是不能修改 [[Configurable]] 和[[Enumerable]]。

        var book = {
          _year: 2004,
          edition: 1
        };
        //定義訪問器的舊有方法
        book.__defineGetter__("year", function(){
          return this._year;
        });
        book.__defineSetter__("year", function(newValue){
          if (newValue > 2004) {
              this._year = newValue;
              this.edition += newValue - 2004;
          }
        });
        book.year = 2005;
        alert(book.edition); //2
  • 定義多個屬性

    • Object.defineProperties()方法:經過描述符一次定義多個屬性

    • 接收兩個對象參數:第一個對象是要添加和修改其屬性的對象,第二個對象的屬性與第一個對象中要添加或修改的屬性一一對應

      var book = {};
      Object.defineProperties(book, {
          _year: {
              value: 2004
          },
          edition: {
              value: 1
          },
          year: {
              get: function(){ 
                  return this._year;
              },
              set: function(newValue){
                  if (newValue > 2004) {
                      this._year = newValue;
                      this.edition += newValue - 2004;
                  }
              }
          }
      });
  • 讀取屬性的特性

    • Object.getOwnPropertyDescriptor()方法;接收兩個參數,屬性所在的對象和要讀取其描述符的屬性名稱;

    • 返回值是一個對象,若是是訪問器屬性,這個對象的屬性有 configurable、enumerable、get 和 set;若是是數據屬性,這個對象的屬性有 configurable、enumerable、writable 和 value

      var book = {};
      Object.defineProperties(book, {
          _year: {
              value: 2004
          },
          edition: {
              value: 1
          },
          year: {
              get: function(){
              return this._year;
          },
          set: function(newValue){
                  if (newValue > 2004) {
                      this._year = newValue;
                      this.edition += newValue - 2004;
                  }
              }
          }
      });
      
      var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
      alert(descriptor.value); //2004
      alert(descriptor.configurable); //false 
      alert(typeof descriptor.get); //"undefined"
      
      var descriptor = Object.getOwnPropertyDescriptor(book, "year");
      alert(descriptor.value); //undefined
      alert(descriptor.enumerable); //false
      alert(typeof descriptor.get); //"function"

二、建立對象

  • Object 構造函數或對象字面量能夠建立單個對象,但使用同一個接口建立不少對象,會產生大量的重複代碼

    var person = new Object();
    var person = {};
  • 工廠模式,解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(對象的類型)

    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 person1 = createPerson("Nicholas", 29, "Software Engineer");
    var person2 = createPerson("Greg", 27, "Doctor");
  • 構造函數模式:

    • 語法:構造函數始終都應該以一個大寫字母開頭,建立實例必須使用 new 操做符

      function Person(name, age, job){
          this.name = name;
          this.age = age;
          this.job = job;
          this.sayName = function(){
              alert(this.name);
          };
      }
      var person1 = new Person("Nicholas", 29, "Software Engineer");
      var person2 = new Person("Greg", 27, "Doctor");
    • 與工廠模式的不一樣之處

      • 沒有顯式地建立對象
      • 直接將屬性和方法賦給了 this 對象
      • 沒有 return 語句
    • 調用構造函數建立實例後臺經歷的步驟:

      • 建立一個新對象
      • 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象)
      • 執行構造函數中的代碼(爲這個新對象添加屬性)
      • 返回新對象
    • 實例具備constructor(構造函數)屬性:用來標識對象類型的

    • 使用instanceof操做符檢測對象類型

      alert(person1 instanceof Object); //true
      alert(person1 instanceof Person); //true
      alert(person2 instanceof Object); //true
      alert(person2 instanceof Person); //true
    • 將構造函數看成函數

      // 看成構造函數使用
      var person = new Person("Nicholas", 29, "Software Engineer");
      person.sayName(); //"Nicholas"
      // 做爲普通函數調用
      Person("Greg", 27, "Doctor"); // 添加到 window
      window.sayName(); //"Greg"
      // 在另外一個對象的做用域中調用
      var o = new Object();
      Person.call(o, "Kristen", 25, "Nurse");
      o.sayName(); //"Kristen"
    • 構造函數的問題:

      • 每一個方法都要在每一個實例上從新建立一遍

        function Person(name, age, job){
          this.name = name;
          this.age = age;
          this.job = job;
          this.sayName = new Function("alert(this.name)");//與聲明函數在邏輯上是等的
        } 
        alert(person1.sayName == person2.sayName); //false
      • 經過把函數定義轉移到構造函數外部來解決這個問題

        function Person(name, age, job){
          this.name = name;
          this.age = age;
          this.job = job;
          this.sayName = sayName;
        }
        function sayName(){
         alert(this.name);
        }
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        //新問題1:在全局做用域中定義的函數實際上只能被某個對象調用,這讓全局做用域有點名存實亡
        //新問題2:若是對象須要定義不少方法,那麼就要定義不少個全局函數,沒有封裝性
  • 原型模式

    • prototype(原型)屬性:每一個函數都有這個屬性,是一個指針,指向一個包含能夠由特定類型的全部實例共享的屬性和方法的對象

      function Person(){};
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
          alert(this.name);
      };
      var person1 = new Person();
      person1.sayName(); //"Nicholas"
      var person2 = new Person(); 
      person2.sayName(); //"Nicholas"
      alert(person1.sayName == person2.sayName); //true
    • 理解原型對象

      • 只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個 prototype(__proto__)屬性,這個屬性指向函數的原型對象,原型對象都會自動得到一個 constructor(構造函數)屬性

      • isPrototypeOf()方法:來肯定對象之間是否存在[[Prototype]]原型關係

        alert(Person.prototype.isPrototypeOf(person1)); //true
        alert(Person.prototype.isPrototypeOf(person2)); //true
      • Object.getPrototypeOf()方法:返回[[Prototype]]的值

        alert(Object.getPrototypeOf(person1) == Person.prototype); //true
        alert(Object.getPrototypeOf(person1).name); //"Nicholas"
      • 多個對象實例共享原型所保存的屬性和方法的基本原理:經過層層搜索查找對象屬性,首先從對象實例自己開始,找不到則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性

      • 能夠經過對象實例訪問保存在原型中的值,但不能經過對象實例重寫原型中的值,當爲對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性

      • 使用 delete 操做符則能夠徹底刪除實例屬性,從而可以從新訪問原型中的屬性

        function Person(){}
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){
          alert(this.name);
        };
        var person1 = new Person();
        var person2 = new Person();
        person1.name = "Greg";
        alert(person1.name); //"Greg"——來自實例
        alert(person2.name); //"Nicholas"——來自原型
        delete person1.name;
        alert(person1.name); //"Nicholas"——來自原型
      • hasOwnProperty()方法:檢測一個屬性是存在於實例中,仍是存在於原型中

        function Person(){}
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function(){ 
            alert(this.name);
        };
        var person1 = new Person();
        var person2 = new Person();
        alert(person1.hasOwnProperty("name")); //false
        person1.name = "Greg";
        alert(person1.name); //"Greg"——來自實例
        alert(person1.hasOwnProperty("name")); //true
        alert(person2.name); //"Nicholas"——來自原型
        alert(person2.hasOwnProperty("name")); //false
        delete person1.name;
        alert(person1.name); //"Nicholas"——來自原型
        alert(person1.hasOwnProperty("name")); //false
    • 原型與 in 操做符

    • 單獨使用in 操做符:在經過對象可以訪問給定屬性時返回 true,不管該屬性存在於實例中仍是原型中

      function Person(){}
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
        alert(this.name);
      };
      
      var person1 = new Person();
      var person2 = new Person();
      
      alert(person1.hasOwnProperty("name")); //false
      alert("name" in person1); //true
      
      person1.name = "Greg";
      alert(person1.name); //"Greg" ——來自實例
      alert(person1.hasOwnProperty("name")); //true
      alert("name" in person1); //true
      
      alert(person2.name); //"Nicholas" ——來自原型
      alert(person2.hasOwnProperty("name")); //false
      alert("name" in person2); //true
      
      delete person1.name;
      alert(person1.name); //"Nicholas" ——來自原型
      alert(person1.hasOwnProperty("name")); //false
      alert("name" in person1); //true
    • hasPrototypeProperty()方法:同時使用 hasOwnProperty()方法和 in操做符,就能夠肯定該屬性究竟是存在於對象中,仍是存在於原型中

      function hasPrototypeProperty(object, name){
        return !object.hasOwnProperty(name) && (name in object);
      } 
      
      function Person(){}
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
        alert(this.name);
      };
      var person = new Person();
      alert(hasPrototypeProperty(person, "name")); //true
      person.name = "Greg";
      alert(hasPrototypeProperty(person, "name")); //false
    • 在for-in 循環裏使用:返回的是全部可以經過對象訪問的、可枚舉的(enumerated)屬性,其中
      既包括存在於實例中的屬性,也包括存在於原型中的屬性

      var o = {
        toString : function(){
            return "My Object";
        }
      };
      for (var prop in o){
        if (prop == "toString"){
        alert("Found toString"); //在 IE 中不會顯示
       }
      }
    • Object.keys()方法:接收一個對象做爲參數,返回一個包含全部可枚舉屬性的字符串數組

      function Person(){}
      Person.prototype.name = "Nicholas";
      Person.prototype.age = 29;
      Person.prototype.job = "Software Engineer";
      Person.prototype.sayName = function(){
        alert(this.name);
      };
      var keys = Object.keys(Person.prototype);
      alert(keys); //"name,age,job,sayName"
      var p1 = new Person();
      p1.name = "Rob";
      p1.age = 31;
      var p1keys = Object.keys(p1);
      alert(p1keys); //"name,age"
    • Object.getOwnPropertyNames()方法:返回全部實例屬性,不管它是否可枚舉

      var keys = Object.getOwnPropertyNames(Person.prototype);
      alert(keys); //"constructor,name,age,job,sayName"
    • 更簡單的原型語法

    • 用一個包含全部屬性和方法的對象字面量來重寫整個原型對象,可是致使constructor 屬性再也不指向 Person

      function Person(){}
      Person.prototype = {
        name : "Nicholas",
        age : 29,
        job: "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
      }; 
      
      var friend = new Person();
      alert(friend instanceof Object); //true
      alert(friend instanceof Person); //true
      alert(friend.constructor == Person); //false
      alert(friend.constructor == Object); //true
    • 解決辦法:從新設置個constructor 屬性的指向,可是致使它的[[Enumerable]]特性被設置爲 true,默認狀況下,原生的 constructor 屬性是不可枚舉的

      function Person(){}
      Person.prototype = {
        constructor : Person,
        name : "Nicholas",
        age : 29,
        job: "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
      };
    • 解決辦法:使用Object.defineProperty()方法重設構造函數

      function Person(){}
      Person.prototype = {
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
        sayName : function () {
            alert(this.name);
        }
      }; 
      //重設構造函數,只適用於 ECMAScript 5 兼容的瀏覽器
      Object.defineProperty(Person.prototype, "constructor", {
        enumerable: false,
        value: Person
      });
    • 原型的動態性

      • 對原型對象所作的任何修改都可以當即從實例上反映出來

        var friend = new Person();
        Person.prototype.sayHi = function(){
          alert("hi");
        };
        friend.sayHi(); //"hi"(沒有問題!)
      • 實例中的指針僅指向原型,而不指向構造函數

        function Person(){}
        var friend = new Person();
        Person.prototype = {
          constructor: Person,
          name : "Nicholas",
          age : 29,
          job : "Software Engineer",
          sayName : function () {
              alert(this.name);
          }
        };
        friend.sayName(); //error 
        //調用構造函數時會爲實例添加一個指向最初原型的[[Prototype]]指針,
        //而把原型修改成另一個對象就等於切斷了構造函數與最初原型之間的聯繫
    • 原生對象的原型

      • 全部原生引用類型(Object、Array、String,等等)都在其構造函數的原型上定義了方法

        alert(typeof Array.prototype.sort); //"function"
        alert(typeof String.prototype.substring); //"function"
      • 經過原生對象的原型,不只能夠取得全部默認方法的引用,並且也能夠定義新方法

        String.prototype.startsWith = function (text) {
          return this.indexOf(text) == 0;
        };
        var msg = "Hello world!";
        alert(msg.startsWith("Hello")); //true
    • 原型對象的問題

      • 全部實例在默認狀況下都將取得相同的屬性值

      • 原型中全部屬性是被不少實例共享的,這種共享對於函數很是合適,對於包含引用類型值的屬性來講會出現問題

        function Person(){}
        Person.prototype = {
          constructor: Person,
          name : "Nicholas",
          age : 29,
          job : "Software Engineer",
          friends : ["Shelby", "Court"],
          sayName : function () {
              alert(this.name);
          }
        };
        var person1 = new Person();
        var person2 = new Person();
        person1.friends.push("Van");
        alert(person1.friends); //"Shelby,Court,Van"
        alert(person2.friends); //"Shelby,Court,Van"
        alert(person1.friends === person2.friends); //true
  • 組合使用構造函數模式和原型模式

    • 構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性
    • 每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存
    • 這種混成模式還支持向構造函數傳遞參數
    function Person(name, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.friends = ["Shelby", "Court"];
    }
    Person.prototype = {
      constructor : Person,
      sayName : function(){
          alert(this.name);
      }
    }
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    var person2 = new Person("Greg", 27, "Doctor");
    person1.friends.push("Van");
    alert(person1.friends); //"Shelby,Count,Van"
    alert(person2.friends); //"Shelby,Count"
    alert(person1.friends === person2.friends); //false
    alert(person1.sayName === person2.sayName); //true
  • 動態原型模式

    • 經過檢查某個應該存在的方法是否有效,來決定是否須要初始化原型

      function Person(name, age, job){
       //屬性
          this.name = name;
          this.age = age;
          this.job = job; //方法
          if (typeof this.sayName != "function"){
              Person.prototype.sayName = function(){
                  alert(this.name);
              };
          }
      } 
      var friend = new Person("Nicholas", 29, "Software Engineer");
      friend.sayName();
    • 使用動態原型模式時,不能使用對象字面量重寫原型,若是在已經建立了實例的狀況下重寫原型,那麼就會切斷現有實例與新原型之間的聯繫

  • 寄生構造函數模式

    • 基本思想:建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象;但
      從表面上看,這個函數又很像是典型的構造函數。構造函數在不返回值的狀況下,默認會返回新對象實例。而經過在構造函數的末尾添加一個 return 語句,能夠重寫調用構造函數時返回的值

      function Person(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 friend = new Person("Nicholas", 29, "Software Engineer");
      friend.sayName(); //"Nicholas"
    • 這個模式能夠在特殊的狀況下用來爲對象建立構造函數。假設咱們想建立一個具備額外方法的特殊數組。因爲不能直接修改 Array 構造函數,所以可使用這個模式。

      function SpecialArray(){
          //建立數組
          var values = new Array();
          //添加值
          values.push.apply(values, arguments);
          //添加方法
           values.toPipedString = function(){
               return this.join("|");
          };
      
          //返回數組
          return values;
      }
      var colors = new SpecialArray("red", "blue", "green");
      alert(colors.toPipedString()); //"red|blue|green"
    • 返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係,不能依賴instanceof操做符來肯定對象類型

  • 穩妥構造函數模式

    • 穩妥對象,指的是沒有公共屬性,並且其方法也不引用 this 的對象;適合在一些安全的環境中(這些環境中會禁止使用 this 和 new),或者在防止數據被其餘應用程序(如 Mashup程序)改動時使用。

    • 穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:

      • 一是新建立對象的實例方法不引用 this
      • 二是不使用 new 操做符調用構造函數
      function Person(name, age, job){
          //建立要返回的對象
          var o = new Object();
          //能夠在這裏定義私有變量和函數
          //添加方法
          o.sayName = function(){
              alert(name);
          };
          //返回對象
          return o;
      } 
      var friend = Person("Nicholas", 29, "Software Engineer");
      friend.sayName(); //"Nicholas
    • 穩妥構造函數模式提供的這種安全性,使得它很是適合在某些安全執行環境——例如,ADsafe(www.adsafe.org)和 Caja(http://code.google.com/p/google-caja/)提供的環境——下使用

    • 使用穩妥構造函數模式建立的對象與構造函數之間也沒有什麼關係,所以 instanceof 操做符對這種對象也沒有意義

三、繼承

  • 原型鏈

    • 基本概念

      • 原型鏈是實現繼承的主要方法:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法
      • 構造函數、原型和實例的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針
      • 讓原型對象等於另外一個類型的實例,此時的原型對象將包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另外一個構造函數的指針。假如另外一個原型又是另外一個類型的實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條
    • 基本模式

      function SuperType(){
          this.property = true;
      }
      SuperType.prototype.getSuperValue = function(){
          return this.property;
      };
      function SubType(){
          this.subproperty = false;
      }
      //繼承了 SuperType
      SubType.prototype = new SuperType();
      SubType.prototype.getSubValue = function (){
          return this.subproperty;
      };
      var instance = new SubType();
      alert(instance.getSuperValue()); //true
    • 注意的地方:

      • 別忘記默認的原型:全部函數的默認原型都是 Object 的實例,所以默認原型都會包含一個內部指針,指向Object.prototype。這也正是全部自定義類型都會繼承 toString()valueOf()等默認方法的根本緣由

      • 肯定原型和實例的關係:instanceof操做符和isPrototypeOf()方法

        //使用instanceof操做符,測試實例與原型鏈中出現過的構造函數,結果就會返回 true
        alert(instance instanceof Object); //true
        alert(instance instanceof SuperType); //true
        alert(instance instanceof SubType); //true 
        
        //isPrototypeOf()方法,原型鏈中出現過的原型,都是該原型鏈所派生的實例的原型,返回 true
        alert(Object.prototype.isPrototypeOf(instance)); //true
        alert(SuperType.prototype.isPrototypeOf(instance)); //true
        alert(SubType.prototype.isPrototypeOf(instance)); //true
      • 謹慎地定義方法:

        //1.給原型添加方法的代碼必定要放在替換原型的語句以後
        function SuperType(){
          this.property = true;
        }
        SuperType.prototype.getSuperValue = function(){
          return this.property;
        };
        function SubType(){
           this.subproperty = false;
        }
        //繼承了 SuperType
        SubType.prototype = new SuperType();
        //添加新方法
        SubType.prototype.getSubValue = function (){
          return this.subproperty;
        };
        //重寫超類型中的方法
        SubType.prototype.getSuperValue = function (){
          return false;
        };
        var instance = new SubType();
        alert(instance.getSuperValue()); //false 
        
        //2.在經過原型鏈實現繼承時,不能使用對象字面量建立原型方法,這樣作就會重寫原型鏈
        function SuperType(){
          this.property = true;
        }
        SuperType.prototype.getSuperValue = function(){
          return this.property;
        };
        function SubType(){
          this.subproperty = false;
        }
        //繼承了 SuperType
        SubType.prototype = new SuperType();
        //使用字面量添加新方法,會致使上一行代碼無效
        SubType.prototype = {
          getSubValue : function (){
              return this.subproperty;
          },
          someOtherMethod : function (){
               return false;
          }
        };
        var instance = new SubType();
        alert(instance.getSuperValue()); //error!
    • 原型鏈的問題

    • 最主要的問題來自包含引用類型值的原型,在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例,因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了

      function SuperType(){
        this.colors = ["red", "blue", "green"];
      }
      function SubType(){}
      //繼承了 SuperType
      SubType.prototype = new SuperType();
      var instance1 = new SubType();
      instance1.colors.push("black");
      alert(instance1.colors); //"red,blue,green,black"
      var instance2 = new SubType();
      alert(instance2.colors); //"red,blue,green,black"
    • 在建立子類型的實例時,不能向超類型的構造函數中傳遞參數

  • 借用構造函數

    • 基本思想:在子類型構造函數的內部調用超類型構造函數

      function SuperType(){
          this.colors = ["red", "blue", "green"];
      }
      function SubType(){
          //繼承了 SuperType
          SuperType.call(this);
      }
      var instance1 = new SubType();
      instance1.colors.push("black");
      alert(instance1.colors); //"red,blue,green,black"
      var instance2 = new SubType();
      alert(instance2.colors); //"red,blue,green"
    • 傳遞參數:能夠在子類型構造函數中向超類型構造函數傳遞參數

      function SuperType(name){
          this.name = name;
      }
      function SubType(){
          //繼承了 SuperType,同時還傳遞了參數
          SuperType.call(this, "Nicholas");
      
          //實例屬性
          this.age = 29;
      }
      var instance = new SubType();
      alert(instance.name); //"Nicholas";
      alert(instance.age); //29
    • 借用構造函數的問題:方法都在構造函數中定義,所以函數複用就無從談起了。並且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果全部類型都只能使用構造函數模式

  • 組合繼承:

    • 基本思想:使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承

      function SuperType(name){
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          alert(this.name); 
      };
      function SubType(name, age){
          //繼承屬性
          SuperType.call(this, name);
          this.age = age;
      }
      //繼承方法
      SubType.prototype = new SuperType();
      SubType.prototype.constructor = SubType;
      SubType.prototype.sayAge = function(){
          alert(this.age);
      };
      var instance1 = new SubType("Nicholas", 29);
      instance1.colors.push("black");
      alert(instance1.colors); //"red,blue,green,black"
      instance1.sayName(); //"Nicholas";
      instance1.sayAge(); //29
      var instance2 = new SubType("Greg", 27);
      alert(instance2.colors); //"red,blue,green"
      instance2.sayName(); //"Greg";
      instance2.sayAge(); //27
    • 組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲 JavaScript 中最經常使用的繼承模式。並且,instanceofisPrototypeOf()也可以用於識別基於組合繼承建立的對象

  • 原型式繼承

    • 思路:藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型

      function object(o){
          function F(){}
          F.prototype = o;
          return new F();
      } 
      var person = {
          name: "Nicholas",
          friends: ["Shelby", "Court", "Van"]
      };
      var anotherPerson = object(person);
      anotherPerson.name = "Greg";
      anotherPerson.friends.push("Rob");
      var yetAnotherPerson = object(person);
      yetAnotherPerson.name = "Linda";
      yetAnotherPerson.friends.push("Barbie");
      alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
    • Object.create()方法:規範化了原型式繼承,這個方法接收兩個參數,一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象

      //在傳入一個參數的狀況下,Object.create()與 object()方法的行爲相同
      var person = {
          name: "Nicholas",
          friends: ["Shelby", "Court", "Van"]
      };
      var anotherPerson = Object.create(person);
      anotherPerson.name = "Greg";
      anotherPerson.friends.push("Rob");
      
      var yetAnotherPerson = Object.create(person);
      yetAnotherPerson.name = "Linda";
      yetAnotherPerson.friends.push("Barbie");
      alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
      
      //第二個參數與Object.defineProperties()方法的第二個參數格式相同:
      //每一個屬性都是經過本身的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性
      var person = {
           name: "Nicholas",
           friends: ["Shelby", "Court", "Van"]
      }; 
      var anotherPerson = Object.create(person, {
          name: {
              value: "Greg"
          }
      });
      
      alert(anotherPerson.name); //"Greg"
  • 寄生式繼承

    • 思路:與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真地是它作了全部工做同樣返回對象

      function createAnother(original){
          var clone = object(original); //經過調用函數建立一個新對象
          clone.sayHi = function(){ //以某種方式來加強這個對象
              alert("hi");
          };
          return clone; //返回這個對象
      }
      var person = {
          name: "Nicholas",
          friends: ["Shelby", "Court", "Van"]
      };
      var anotherPerson = createAnother(person);
      anotherPerson.sayHi(); //"hi"
    • 在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式繼承也是一種有用的模式。使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用而下降效率;這一點與構造函數模式相似

  • 寄生組合繼承

    • 組合繼承最大的問題:就是不管什麼狀況下,都會調用兩次超類型構造函數,一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部

      //子類型最終會包含超類型對象的所有實例屬性,但咱們不得不在調用子類型構造函數時重寫這些屬性
      function SuperType(name){ 
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          alert(this.name);
      };
      function SubType(name, age){
          SuperType.call(this, name); //第二次調用 SuperType()
          this.age = age;
      }
      SubType.prototype = new SuperType(); //第一次調用 SuperType()
      SubType.prototype.constructor = SubType;
      SubType.prototype.sayAge = function(){
          alert(this.age);
      };
    • 寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法;其背後的基本思路是:沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型

      function inheritPrototype(subType, superType){
          var prototype = object(superType.prototype); //建立對象
          prototype.constructor = subType; //加強對象
          subType.prototype = prototype; //指定對象
      }
      function SuperType(name){
          this.name = name;
          this.colors = ["red", "blue", "green"];
      }
      SuperType.prototype.sayName = function(){
          alert(this.name);
      };
      function SubType(name, age){
          SuperType.call(this, name);
          this.age = age;
      }
      inheritPrototype(SubType, SuperType);
      SubType.prototype.sayAge = function(){
          alert(this.age);
      };
    • YUI 的 YAHOO.lang.extend()方法採用了寄生組合繼承,從而讓這種模式首次出如今了一個應用很是普遍的 JavaScript 庫中,要了解有關 YUI 的更多信息,請訪問http://developer. yahoo.com/yui/

四、總結

ECMAScript支持面向對象(OO)編程,但不使用類或者接口。對象能夠在代碼執行過程當中建立和加強,所以具備動態性而非嚴格定義的實體。在沒有類的狀況下,能夠採用下列模式建立對象

  • 工廠模式,使用簡單的函數建立對象,爲對象添加屬性和方法,而後返回對象。這個模式後來被構造函數模式所取代
  • 構造函數模式,能夠建立自定義引用類型,能夠像建立內置對象實例同樣使用 new 操做符。不過,構造函數模式也有缺點,即它的每一個成員都沒法獲得複用,包括函數。因爲函數能夠不侷限於任何對象(即與對象具備鬆散耦合的特色),所以沒有理由不在多個對象間共享函數
  • 原型模式,使用構造函數的 prototype 屬性來指定那些應該共享的屬性和方法。組合使用構造函數模式和原型模式時,使用構造函數定義實例屬性,而使用原型定義共享的屬性和方法

JavaScript 主要經過原型鏈實現繼承。原型鏈的構建是經過將一個類型的實例賦值給另外一個構造函數的原型實現的。這樣,子類型就可以訪問超類型的全部屬性和方法,這一點與基於類的繼承很類似。原型鏈的問題是對象實例共享全部繼承的屬性和方法,所以不適宜單獨使用。解決這個問題的技術是借用構造函數,即在子類型構造函數的內部調用超類型構造函數。這樣就能夠作到每一個實例都具備本身的屬性,同時還能保證只使用構造函數模式來定義類型。使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,而經過借用構造函數繼承實例屬性。此外,還存在下列可供選擇的繼承模式:

  • 原型式繼承,能夠在沒必要預先定義構造函數的狀況下實現繼承,其本質是執行對給定對象的淺複製。而複製獲得的副本還能夠獲得進一步改造
  • 寄生式繼承,與原型式繼承很是類似,也是基於某個對象或某些信息建立一個對象,而後加強對象,最後返回對象。爲了解決組合繼承模式因爲屢次調用超類型構造函數而致使的低效率問題,能夠將這個模式與組合繼承一塊兒使用
  • 寄生組合式繼承,集寄生式繼承和組合繼承的優勢與一身,是實現基於類型繼承的最有效方式
相關文章
相關標籤/搜索