js高級程序設計-面向對象的程序設計-閱讀筆記

ECMAScript 中又兩種屬性:數據屬性(包含一個數據值的位置叫作數據屬性)和訪問器屬性(getter()setter()就是訪問器屬性)chrome

  1. 數據屬性而數據屬性又有四個這樣的特性:數組

    • [Configurable] 是否可配置,編輯,刪除屬性,默認true瀏覽器

    • [Enumberable]是否能夠被枚舉,便可被for-in,默認true安全

    • [Writable] 是否可寫,默認true,寫不表明刪除,這裏僅限修改函數

    • [Value] 屬性的數據值,默認是undefinedthis

    var person = {};//設置了一個空對象
      //定義了對象的默認屬性 name 的一些屬性的特性
      Object.defineProperty(person,"name",{
          writable:false,// 不能夠編輯
          value:"niconico" //默認的值就是 niconico
      });
    
      console.log(person.name);//返回niconico,由於默認值,不用設置也有值
      person.name = "gggg";//即便設置了name 新值,由於不可編輯,因此沒變化
      console.log(person.name);//返回niconico

須要注意的是[Configurable]被改 false 以後沒辦法改回來 truespa

  1. 訪問器屬性(getter() 和 setter()),而他們又有這樣的特性:firefox

    • [Configurable] 跟以前同樣prototype

    • [Enumberable] 跟以前同樣指針

    • [Get] 在讀取屬性時調用的函數,默認是 undefined

    • [Set] 在寫入屬性時調用的函數,默認是 undefined

    var book = {
          _year: 2004, //經常使用語法,表明只能經過對象方法訪問的屬性
          edition: 1
      };
    
      Object.defineProperty(book, "year", {
          get: function () { //定義一個 getter
              return this._year; //直接讀取屬性
          },
          //若是不設置 setter的話那麼這個對象的屬性就沒辦法修改
          set: function (newValue) { //定義一個 setter
              if (newValue > 2004) {
                  this._year = newValue; //若是註釋掉,那麼_ year 不會改變
                  this.edition += newValue - 2004;
              }
          }
      });
    
      book.year = 2005;
      //由於這個函數的 setter 裏面也一塊兒把_year修改了,因此可以看到被修改
      console.log(book.year); //返回2005
      console.log(book.edition);//返回2

數據屬性和訪問器屬性的區分

var book = {};

    Object.defineProperties(book, { //這裏用了defineProperties定義多個屬性
        _year: { //數據屬性
            value: 2004
        },
        edition: { //數據屬性
            value: 1
        },
        year: {//訪問器屬性,判斷的標準就是是否有 getter 或者 setter
            get: function () {
                return this._year;
            },
            set: function (newValue) {
                if (newValue > 2004) {
                    this._year = newValue;
                    this.edition += newValue - 2004;
                }
            }
        }
    })
    
    
    //獲取屬性的特性
    var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
    console.log(descriptor.value); //獲取值這個特性
    console.log(descriptor.configurable); //獲取 configurable 這個特性

建立對象

  • 工廠模式:用函數封裝以特定的接口建立對象,無法建立特定類型的對象

  • 構造函數模式: 構造函數能夠用來建立特定類型的對象,可是每一個成員沒法複用

  • 原型模式:使用構造函數的 prototype 屬性來指定那些應該共享的屬性和方法

  • 組合繼承: 使用構造函數模式和原型模式時,使用構造函數定義實例屬性,而使用原型定義共享的屬性和方法
    動態原型模式:能夠在沒必要預先定義構造函數的狀況下實現繼承,其本質是執行給指定對象的淺複製

  • 寄生構造函數模式:基於某個對象或某些信息建立一個對象,而後加強對象,最後返回對象

  • 穩妥構造函數模式:集寄生式繼承和組合繼承的優勢


工廠模式

這種模式抽象了建立具體對象的過程,由於 ECMAScript 中沒法建立類,因此用函數封裝以特定的接口建立對象

function createPerson(name,age,job) {
        var o = new Object(); //代替建立對象
        o.name = name;//代替設置屬性
        o.age = age;
        o.job = job;
        o.sayName = function () { // 代替設置方法
            console.log(this.name);
        };
        return o; //返回是一個對象
    }

    var person1 = createPerson("nico",29,"soft");
    var person2 = createPerson("gg",30,"dog");
    console.log(person1);//返回Object {name: "nico", age: 29, job: "soft"}
    console.log(person2);

優勢:
1.建立對象的方式簡單了
缺點:
1.沒有解決對象類型識別的問題,由於都是直接new Object, 都是 Object,因此無法區分是具體哪個對象類型

構造函數模式

實際上構造函數經歷瞭如下過程:

  1. 建立一個新對象

  2. 將構造函數的做用域賦給新對象(所以this指向了這個新對象)

  3. 執行構造函數中的代碼(爲這個新對象添加屬性)

  4. 返回新對象

function Person(name,age,job) {  //標準寫法,構造函數名第一個大寫
        this.name = name; 
        this.age = age;
        this.job = job;
        this.sayName = function () {
            console.log(this.name);
        }
        //不須要return,由於會自動返回新對象,若是使用了 return 就會改變了返回的內容
    }

        var person1 = new Person("nico",29,"soft");//用new
        var person2 = new Person("gg",30,"dog");
        console.log(person1);//返回Person {name: "nico", age: 29, job: "soft"}
        console.log(person2);
        
        
    //這些實例都是Object 對象,也是Person 對象
    console.log(person1 instanceof Object);//返回 true
    console.log(person1 instanceof Person);//返回 true
    //person1和 person2分別保存着Person 一個不一樣的實例,這兩個實例都有一個constructor(構造函數)屬性,都指向Person, 說明他們都是同一個構造函數建立的
    console.log(person1.constructor == Person);//返回 true
    console.log(person2.constructor == Person);//返回

構造函數與其餘函數的惟一區別就在於調用他們的方式不一樣,任何函數,只要經過 new 來調用,那他就能夠做爲構造函數,而任何函數,若是不經過 new 來調用,那他就跟普通函數也不會有兩樣.

構造函數也能夠做爲普通函數使用

function Person(name, age, job) {
        this.name = name; //直接賦值給 this, 即直接設置當前對象的屬性
        this.age = age;
        this.job = job;
        this.sayName = function () {
            console.log(this.name);
        }
        //不須要return, 也不須要返回對象
    }
    // 做爲構造函數調用
    var person = new Person("pp", 10, "kk");
    person.sayName();//返回 pp
    //做爲普通函數調用
    Person("yy", 20, "gg");//這裏添加到 window 對象了,由於默認全局做用域
    window.sayName();//返回 yy
    //在另一個對象的做用域中調用
    var o = new Object();
    Person.call(o, "aa", 25, "bb"); //由於是被 o 調用,因此 this 指向 o
    o.sayName();//返回 aa

優勢:
1.能夠知道對象實例是是哪一個對象類型,即構造函數是誰(經過 instanceOf() 或者 constructor 來驗證)
缺點:
1.每一個方法都要在每一個實例上從新建立一遍,會致使不一樣的做用域鏈和標示符解析問題,例如兩個實例之間的方法並不能用== 來判斷,例如 person1.sayName == person2.sayName 是返回 false 的,由於都是新建立的實例,都是獨立的

原型模式

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

  • 換句話說,沒必要再構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中

function Person() {} //初始化一個空對象

    Person.prototype.name = "nico"; //直接將屬性寫到原型裏面
    Person.prototype.sayName = function () {//直接將方法寫到原型裏面
        console.log(this.name);
    };

    //原型的全部屬性和方法被全部實例共享
    var person1 = new Person();
    person1.sayName();//返回 nico

    var person2 = new Person();
    person2.sayName();//返回 nico

    //他們其實都指向同一個原型的同一個方法,因此 true
    console.log(person1.sayName() == person2.sayName());//返回true

優勢:
1.可讓全部對象實例共享它所包含的屬性和方法
缺點:
1.實例都須要有隻屬於本身的屬性,而原型對象是徹底共享的,因此不多有人單獨使用原型模式

理解原型對象

  • 在腳本中沒有標準的方式訪問[prototype],可是firefox,safari,chrome在每一個對象上都支持一個_proto_

  • 建立了自定義的構造函數以後,其原型對象默認只會取得constructor屬性,當調用構造函數建立一個新實例後,該實例的內部將包含一個指針指向構造函數的運行對象.

圖片描述

  1. Person 是構造函數,Person.prototype是原型對象,person1 和 person2 是實例

  2. Person.prototype的constructor指向Person,由於原型對象是構造函數建立的,因此 constructor 指向Person

  3. Person的prototype 指向了原型對象,而又由於默認狀況下,全部的原型對象的 constructor 都是在被建立的時候指向構造函數

  4. person1和person2 有一個內部屬性[prototype],指向Person.prototype,實例的prototype 指向原型對象很正常

  5. 經過isPrototypeOf()來肯定對象之間是否存在實例和原型對象的關聯關係

    //若是[prototype]指向調用isPrototypeOf方法的對象的話,那麼就會返回 true
    console.log(Person.prototype.isPrototypeOf(person1)); //返回 true
    console.log(Person.prototype.isPrototypeOf(person2)); //返回 true
  6. 經過 getPrototypeOf 方法來獲取原型的屬性

    //getPrototypeOf返回的對象是原型對象
    console.log(Object.getPrototypeOf(person1) == Person.prototype);//返回 true
    console.log(Object.getPrototypeOf(person1).name); //即便這個實例沒有設置屬性 name, 也能夠獲取原型對象的屬性 name
  7. 用 hasOwnProperty() 方法檢測一個屬性是否存在實例中(返回 true),仍是存在與原型中(返回 false)

function Person() {} //初始化一個空對象
    Person.prototype.name = "nico";
    var person1 = new Person();
    var person2 = new Person();
    //沒有這個屬性也會返回 false
    console.log(person1.hasOwnProperty("name"));//返回 false

    person1.name="bbb";//設置 person1的name
    console.log(person1.name); //返回 bbb
    console.log(person1.hasOwnProperty("name"));//返回true

    //沒有設置,使用的是原型的 name,即便不存在實例中的時候
    console.log(person2.name);//返回 nico
    console.log(person2.hasOwnProperty("name"));//返回 false

每當代碼讀取某個對象的某個屬性的時候,都會執行一次搜搜,搜索搜索對象實例自己,若是沒有,就去搜索原型對象

  1. 同時使用 in 和hasOwnProperty就能肯定該屬性是存在對象中仍是存在原型中

  2. 只能肯定是否存在實例中,但區分不了是對象仍是原型,hasOwnProperty只能確認是否存在實例中,因此二者結合能夠實現判斷

function hasPrototypeProperty(object,name) {
        //屬性不存在於實例中 而且屬性存在於對象中就返回 true    
        return !object.hasOwnProperty(name) && (name in object);
    }
  1. 在 for-in 循環時,返回的是全部可以經過對象訪問的,可枚舉的屬性,其中包括實例中的屬性和原型中的屬性

  2. 用 Object.keys() 方法返回全部可枚舉的屬性, Object.getOwnPropertyNames能夠返回全部屬性,包括不可枚舉的屬性

function Person() {
    }
    Person.age = 19;
    Person.prototype.name = "nico";
    var keys1 = Object.keys(Person);//Person 的屬性
    console.log(keys1); //返回["age"],數組
    var keys2 = Object.keys(Person.prototype);//Person的原型對象屬性
    console.log(keys2);//返回["name"],數組
    
     //getOwnPropertyNames能夠返回全部屬性,包括不可枚舉的屬性,例如constructor
    var keys3 = Object.getOwnPropertyNames(Person);
    console.log(keys3); //返回["length", "name", "arguments", "caller", "prototype", "age"]
    var keys4 = Object.getOwnPropertyNames(Person.prototype);
    console.log(keys4); //返回["constructor", "name"]

Object.keys()和Object.getOwnPropertyNames()均可不一樣程度的代替for-in, 不過須要比較新的瀏覽器

  • 更簡單的原型語法,封裝原型

function Person() {
    }
//字面量建立對象語法
    Person.prototype = {
        constructor: Person, 
        name: "nico",
        age: 18,
        sayName: function () {
            console.log(this.name);
        }
    }

須要注意的是這種寫法的話, constructor屬性再也不指向Person,由於沒建立一個函數,就會同時建立他的 prototype 對象,這個對象自動得到 constructor 屬性,而字面量語法會重寫這個 prototype 對象,所以 constructor 屬性也就變成了新的對象的 constructor 屬性(指向 Object),因此須要另外指定一個 constructor

原型的動態性

因爲在原型中查找值的過程是一次搜索,所以咱們隊原型對象所作的任何修改都可以當即從實例上反映出來

function Person1() {};
    var friend = new Person1(); //先與修改原型前建立了實例,但也能使用這個原型方法
    Person1.prototype.sayHi = function () { 
        console.log("hi");
    };
    //先找本身,而後找原型
    friend.sayHi();//返回 hi

緣由:實例與原型之間的鬆散連接關係
當咱們調用 friend.sayHi( )時,首先會在實例中搜索名爲 sayHi 的屬性,沒找到以後會繼續搜索原型,由於實例與原型之間的連接只不過是一個指針,而非一個副本,所以就能夠在原型中找到新的 sayHi 屬性並返回保存在那裏的函數

  • 重寫原型切斷了現有原型與任何以前已經存在的對象實例之間的聯繫
    調用構造函數時會爲實例添加一個指向最初原型的[prototype]指針,而把原型修改成另一個對象就等於切斷了構造函數與最初原型之間的聯繫

function Person() {
    }
    //重寫原型以前
    var friend = new Person();

    Person.prototype.sayName = function () {
        console.log("hi");
    };
     friend.sayName();//返回 hi,
    //重寫原型以後(註釋了)
//    Person.prototype = {
//        constructor: Person,
//        name: "nico",
//        age: 18,
//        sayName: function () {
//            console.log(this.name);
//        }
//    };

    friend.sayName();//直接報錯

圖片描述

1.字面量寫法修改原型對象會重寫這個原型對象
2.實例中的指針僅指向原型,而不指向構造函數
3.由於他會建立一個新的原型對象,原有的實例會繼續指向原來的原型,可是全部的屬性和方法都存在於新的原型對象裏面,因此沒辦法使用這些屬性和方法
4.並不推薦在產品化得程序中修改原生對象的原型,可能會影響了其餘使用原生對象的原型的代碼

組合使用構造函數模式和原型模式(經常使用)

  • 構造函數用於定義實例屬性,原型模式用於定義方法和共享的屬性.

  • 每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,這種模式還支持向構造函數傳遞參數

function Person(name, age, job) {
        //實例屬性在構造函數中定義
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ["tom", "jim"];
    }

    Person.prototype = {
        //共享的方法在原型中定義
        constructor: Person,
        sayName: function () {
            console.log(this.name);
        }
    };

    var person1 = new Person("Nico", 29, "software eng");
    var person2 = new Person("Greg", 30, "doctor");

    person1.friends.push("Vivi");//單獨添加 person1實例的數組數據
    console.log(person1.friends);//返回["tom", "jim", "Vivi"]
    console.log(person2.friends);//返回["tom", "jim"]
    console.log(person1.friends === person2.friends); //返回 false,沒共享 friends 數組
    console.log(person1.sayName === person2.sayName); //返回 true ,共享了其餘方法

動態原型模式

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

  • 把全部信息都封裝在構造函數,經過在構造函數中初始化原型

function Person(name, age, job) {
        //實例屬性在構造函數中定義
        this.name = name;
        this.age = age;
        this.job = job;
        //只在sayName方法不存在的時候才添加原型中
        if (typeof this.sayName != "function") {
            Person.prototype.sayName = function () {
                console.log(this.name);
            }
        }
    }

    var friend = new Person("jack", 29, "soft ware eng");
    friend.sayName();
  1. 對原型修改的話,不能使用字面量重寫,由於會斷開跟原型的關聯

寄生構造函數模式(parasitic)(不建議使用)

  • 建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象.

  • 這個代碼幾乎跟工廠模式同樣,惟一區別是如何調用,工廠模式沒有 new, 這個有 new

function Person(name, age, job) {
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function () {
            console.log(this.name);
        };
        //在構造函數裏面添加一個 return 會重寫調用構造函數時返回的值
        //不寫 return 的話,默認會返回新對象實例
        return o;
    }
    //用 new 方式來調用
    var friend = new Person("jack", 29, "soft ware eng"); 
    friend.sayName(); //返回的實例就是 Person 函數裏面新建立的那個指定實例,因此有這個實例的全部屬性和方法
  1. 返回的對象與構造函數或者構造函數原型屬性之間沒有關係,因此不能使用 instanceof 來肯定對象類型,不建議使用這種模式

穩妥構造函數模式(較少使用)

  • 穩妥對象durable object指的是沒有公共屬性,並且其方法也不引用 this 的對象,主要用在一些安全的環境中,禁止使用this 和 new 之類的,或者在防止數據被其餘應用程序改動時使用

function Person(name, age, job) {
        //建立要返回的對象
        var o = new Object(); // 這個就是一個穩妥對象,由於單獨獨立
        //能夠在這裏定義私有變量和函數
        //添加方法
        o.sayName = function () {
            console.log(name);
        };
        //返回對象
        return o;
    }

    var friend = Person("nico", 29, "software eng");
    friend.sayName(); //返回 nico

繼承

  • 實現繼承:表示一個類型派生於一個基類型,擁有該基類型的全部成員字段和函數。

  • 接口繼承:表示一個類型只繼承了函數的簽名,沒有繼承任何實現代碼。

  • 一個函數由這麼幾部分組成,函數名、參數個數、參數類型、返回值,函數簽名由參數個數與其類型組成。函數在重載時,利用函數簽名的不一樣(即參數個數與類型的不一樣)來區別調用者到底調用的是那個方法!函數簽名由函數的名稱和它的每個形參(按從左到右的順序)的類型和種類(值、引用或輸出)組成。

  • 由於 ECMAScript 中函數沒有簽名,因此沒法實現接口繼承

  • ECMAScript 只支持實現繼承,並且其實現繼承主要是依靠原型鏈來實現.

原型鏈

  • 實現繼承主要是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法

  • 構造函數,原型和實例的關係:

    • 每一個構造函數都有一個原型對象

    • 原型對象都包含一個指向構造函數的指針

    • 實例都包含一個指向原型對象的內部指針

  • 假如咱們讓原型對象等於另一個類型的實例,此時,原型對象將包含一個指向另外一個原型的指針,相應地,另外一個原型中也包含着一個指向另一個構造函數的指針,如此類推

(我把 SuperType 的 prototype 屬性換了一個名字testprototype,方便理解)

  1. instance 指向 SubType 的原型, SubType 的原型又指向了 SuperType 的原型, getSuperValue 方法仍然還在 SuperType.prototype 中,可是property(testprototype) 則位於 SubType.prototype 中,這是由於 prototype(testprototype)是一個實例屬性,而 getSuperValue() 則是一個原型方法.既然 SubType.prototype 如今是 SuperType 的實例,那麼 property(testprototype)就位於該實例中.

  2. instance.constructor 如今指向SuperType, 這是由於 SubType 的原型指向了另一個對象-- SuperType 的原型,而這個原型對象的 constructor 屬性指向 SuperType

//假設這個類型要被繼承方
    function SuperType() {
        //屬性
        this.testSuperprototype = true;
    }
    //原型的方法
    SuperType.prototype.getSuperValue = function () {
        return this.testSuperprototype;
    };
    //假設這個類型是繼承方
    function SubType() {
        //屬性
        this.subproperty = false;
    }

    //SubType繼承於SuperType,將實例賦給SubType.prototype(Subtype的原型),
    //實現的本質就是重寫了SubType的原型對象
    SubType.prototype = new SuperType();
    //集成以後,設置SubType的原型的方法
    SubType.prototype.getSubValue = function () {
        return this.subproperty;//獲取subproperty屬性,若是沒有繼承的話,那麼這裏是 false
                                //繼承以後就改變了
    };
    var instance = new SubType();
    console.log(instance.getSuperValue()); //返回 true
  1. 繼承經過建立 SuperType 的實例,而後賦給 Subtype.prototype 原型實現的,原來存在於 SuperType 的實例的全部屬性和方法,如今也存在於 SubType.prototype 中了

  2. 確立繼承關係以後,咱們給 Subtype.prototype 添加了一個方法,這樣就在繼承了 SuperType 的屬性和方法的基礎上有添加了一個新方法

這是完整的原型鏈圖,由於還要包含 Object, 不過總的來講基本差很少,例如,若是調用 instance的 toString()方法,其實就是調用 Object 的 toString()

肯定原型和實例的關係

//由於原型鏈的關係, instance都是Object 或者SuperType 或者SubType 任何一個類型的實例
    console.log(instance instanceof Object);//true
    console.log(instance instanceof SuperType);//true
    console.log(instance instanceof SubType);//true
    //只要在原型鏈出現過的原型,均可以說是該原型鏈所派生的實例的原型
    console.log(Object.prototype.isPrototypeOf(instance));//true
    console.log(SuperType.prototype.isPrototypeOf(instance));//true
    console.log(SubType.prototype.isPrototypeOf(instance));//true

謹慎地定義方法

給原型添加方法的代碼必定要放在替換原型的語句以後,否則就會覆蓋了超類中的方法了.

//假設這個類型要被繼承方
        function SuperType() {
            //屬性
            this.testSuperprototype = true;
        }
        //原型的方法
        SuperType.prototype.getSuperValue = function () {
            return this.testSuperprototype;
        };
        //假設這個類型是繼承方
        function SubType() {
            //屬性
            this.subproperty = false;
        }

        //SubType繼承於SuperType,將實例賦給SubType.prototype(Subtype的原型)
        SubType.prototype = new SuperType();
        //繼承以後,設置SubType的原型的方法
        SubType.prototype.getSubValue = function () {
            return this.subproperty;//獲取subproperty屬性,若是沒有繼承的話,那麼這裏是 false
                                    //繼承以後就改變了
        };
        //重寫超類型(被繼承的類型)中的方法
        SubType.prototype.getSuperValue = function () {
          return false;   //返回的是這個,而不是 true(被繼承的類型中是 true)
        };
        var instance = new SubType();
        console.log(instance.getSuperValue()); //返回 false

在經過原型鏈實現繼承時,不能使用對象字面量建立原型方法,由於這樣會重寫原型鏈的

借用構造函數constructor stealing(不多用)

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

function SuperType() {
        this.colors = ["red", "blue", "green"];
    }
    function SubType() {
        ///call 的方式以SubType的身份來調用SuperType的構造函數,
        //這麼作能夠將SuperType的構造函數的屬性傳到SubType上,
        SuperType.call(this); 
    }
    var instance1 = new SubType();
    instance1.colors.push("black");
    console.log(instance1.colors);//返回["red", "blue", "green", "black"]

    var instance2 = new SubType();
    console.log(instance2.colors);//返回["red", "blue", "green"]

優勢:
1.能實現繼承
缺點:
1.由於使用 call 的方式即便能夠調用超類來實現繼承,可是超類的原型屬性和方法都不能使用,由於 call 只是改變 this, 沒有改變 constructor 指向

組合繼承combination inheritance(經常使用)

  • 將原型鏈和借用構造函數技術組合到一塊兒

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

  • 既經過原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性

//設置一個超類,即SuperType的構造函數,裏面有2個屬性
    function SuperType(name) {
        this.name = name;
        this.colors = ["red", "blue"];
    }
    //設置一個超類,即SuperType的原型方法 sayName()
    SuperType.prototype.sayName = function () {
        console.log(this.name);
    };
    //設置一個子類,即SubType的構造函數
    function SubType(name, age) {
        //call 的方式以SubType的身份來調用SuperType的構造函數,
        //這麼作能夠將SuperType的構造函數的屬性傳到SubType上,可是由於call 只能改變 this 指向,改變不了constructor, 因此沒辦法得到超類的原型方法
        //這樣的話就將超類的屬性放到子類裏面,因此在實例化子類以後,即便改變了其中一個子類實例的屬性,也不會影響其餘的子類實例
        SuperType.call(this, name);////第二次調用超類SuperType
        this.age = age; //也設置了本身自己的屬性(方便區分)
    }
    //將超類SuperType實例化,並賦值給SubType的原型
    //SubType的原型被改寫了,如今就是SuperType實例了,這樣就能夠獲取到SuperType的原型方法了
    SubType.prototype = new SuperType();//第一次調用超類SuperType
    //定義一個本身的原型方法(方便區分)
    //這個須要在原型被改寫完成後才能作,否則的話會被覆蓋
    SubType.prototype.sayAge = function () {
        console.log(this.age);
    };

    var instance1 = new SubType("nico", 20); 
    instance1.colors.push("black"); //instance1改變了,不過 instance2不會改變
    console.log(instance1.colors); //返回["red", "blue", "black"]
    instance1.sayName();//返回 nico,這是超類的原型方法,拿到子類用
    instance1.sayAge();//返回20,這是子類自定義的原型方法,同樣能夠用

    var instance2 = new SubType("greg", 29);
    console.log(instance2.colors);//返回["red", "blue"]
    instance2.sayName();//返回 greg
    instance2.sayAge();//返回29

備註:

  1. 須要理解原型鏈的知識

  2. 須要理解構造函數的執行過程

  3. 使用這種方式實現繼承, 子類可以調用超類的方法和屬性,由於超類的原型也賦值給子類了,真正實現了複用和繼承,並且也可以保證各自實例的屬性互不干涉,由於屬性都在new 構建的時候生成,每一個實例都單獨生成

  4. 第一次調用超類會獲得兩個屬性 name 和 colors,他們是超類 SuperType 的屬性,不過如今位於子類 SubType 的原型中,第二次調用超類會建立新的兩個屬性 name 和 colors, 他們會覆蓋掉子類 SubType原型中的兩個同名屬性

缺點:
1.會調用兩次超類型構造函數
2.不得不在調用子類型構造函數時重寫屬性

原型式繼承

  • 必須有一個對象做爲另一個對象的基礎

  • 在不必建立構造函數,只想讓一個對象與另一個對象保持相似的狀況下,可使用這個方式,須要注意的就是共享的問題

function object(o) {
        function F() { //建立一個臨時性的構造函數
        }
        F.prototype = o;//將傳入的對象做爲這個構造函數的原型
        return new F();//返回這個臨時構造函數的新實例
    }

    var person = {
        name: "nico",
        friends: ["a", "b"]
    };
    //傳入 person 對象,返回一個新的實例,這個實例也是傳入的 person 對象做爲原型的
    //因此可使用它的屬性和方法
    var anotherPerson = object(person);
    anotherPerson.name = "gg";
    anotherPerson.friends.push("rr");

    //由於是使用同一個對象做爲原型,因此跟原型鏈差很少,會共享這個原型對象的東西
    var yetAnotherPerson = object(person);
    yetAnotherPerson.name = "ll";
    yetAnotherPerson.friends.push("kk");

    console.log(person.friends);//返回["a", "b", "rr", "kk"]
    console.log(person.name);//返回nico, 由於基本類型值是不會變化的

在 ECMAScript 5下有一個 Object.create 方法跟他差很少

var person = {
            name: "nico",
            friends: ["a", "b"]
        };
        
        var anotherPerson = Object.create(person);
        anotherPerson.name = "gg";
        anotherPerson.friends.push("rr");
    
        var yetAnotherPerson = Object.create(person);
        yetAnotherPerson.name = "ll";
        yetAnotherPerson.friends.push("kk");
        //結果同樣
        console.log(person.friends);//返回["a", "b", "rr", "kk"]

另外Object.create 支持第二個參數,能夠指定任何屬性覆蓋原型對象的同名屬性

var person = {
        name: "nico",
        friends: ["a", "b"]
    };

    var anotherPerson = Object.create(person, {
        name: { //以傳入一個對象的方式, key 就是屬性名
            value: "lala"
        }
    });
    
    console.log(anotherPerson.name);//返回 lala

寄生式繼承

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

  • 任何可以返回新對象的函數都適用於此模式

  • 跟構造函數模式相似,不能作到函數複用.

function object(o) {
        function F() { //建立一個臨時性的構造函數
        }

        F.prototype = o;//將傳入的對象做爲這個構造函數的原型
        return new F();//返回這個臨時構造函數的新實例
    }
    //至關於扔了兩次,第一次扔給一個臨時的構造函數,生成一個實例
    //第二次再扔給一個固定變量,而後在這裏去給予屬性和方法
    function createAnother(original) {
        var clone = object(original);
        clone.sayHi = function () { //能夠本身添加方法
            console.log("hi");
        };
        return clone;
    }

    var person = {
        name: "nico",
        friends: ["a", "b"]
    };

    var anotherPerson = createAnother(person);
    anotherPerson.sayHi();//返回 hi

寄生組合式繼承

  • 經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法

  • 基本思路,使用寄生式繼承來繼承超類型的原型,而後再將結果指定給予子類型的原型

function object(o) {
        function F() { //建立一個臨時性的構造函數
        }

        F.prototype = o;//將傳入的對象做爲這個構造函數的原型
        return new F();//返回這個臨時構造函數的新實例
    }
    //兩個參數,一個是子類型函數,一個是超類型構造函數
    function inheritPrototype(subType, superType) {
        //建立一個超類型原型的一個副本
        var prototype = object(superType.prototype);//建立對象
        //爲建立的副本添加 constructor 屬性,彌補因重寫原型而失去默認的 constructor 屬性
        prototype.constructor = subType;//加強對象
        //將新建立的對象賦值給子類型的原型,這樣子類型就完成了繼承了
        subType.prototype = prototype;//指定對象
    }


    function SuperType(name) {
        this.name = name;
        this.colors = ["red", "blue"];
    }
    SuperType.prototype.sayName = function () {
        console.log(this.name);
    };
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    //能夠看到,這裏少調用了一次超類的構造函數
    inheritPrototype(SubType, SuperType);
    SubType.prototype.sayAge = function () {
        console.log(this.age);
    };

    var test = new SubType("aa", 100);
    test.colors.push("white");
    console.log(test.colors); //["red", "blue", "white"]
    test.sayName();//aa
    test.sayAge();//100

    var test1 = new SubType("pp", 1);
    test1.colors.push("black");
    console.log(test1.colors);//["red", "blue", "black"]
    test1.sayName();//pp
    test1.sayAge();//1
相關文章
相關標籤/搜索