《JavaScript高級程序設計》——對象學習筆記

建立對象

使用對象字面量的形式一個接口會建立不少對象, 會產生大量的重複代碼。編程

工廠模式:用函數來封裝以特定接口建立對象的細節

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("Simon", 29, "software Engineer");
var person2 = createPerson("Zaynex",22, "Doctor");
  • 這種模式解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)數組

構造函數模式

- 可用於建立特定模式的對象,像Object、Array等原生構造函數,在運行時會自動出如今執行環境中。

咱們利用構造函數重寫下剛纔的函數。瀏覽器

function createPerson(name, age, job)
    {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function()
        {
            alert(this.name);
        };
    }

    var person1 = new Person("Simon",29, "software Engineer");
    var person2 = new Person("Simon",29, "software Engineer");

構造函數與工廠模式的差別

  1. 沒有顯示地建立對象;安全

  2. 直接將屬性和方法賦給this對象;app

  3. 沒有 return 語句;函數

咱們注意到Person開頭是大寫,按照慣例來說,構造函數開頭字母是大寫,非構造函數以小寫字母開頭。測試

調用構造函數四步驟

  1. 建立一個新對象;this

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

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

  4. 返回新對象

person1和person2都保存着Person的一個不一樣的實例。這兩個對象都有一個constructor(構造函數)屬性,該屬性指向Person。

alert(person1.constructor == Person) //true;
alert(person2.constructor == Person) //true;

對象的constructor屬性最初是用來標識對象類型的。可是提到檢測對象類型,仍是instanceof操做符更可靠一些。

alert(person1 instanceof Object);
alert(person1 instanceof Person);
alert(person2 instanceof Object);
alert(person2 instanceof Person);
//都爲true.

咱們所建立的全部對象都是Object的實例,同時也是Person的實例。

  • 建立自定義的構造函數意味着未來能夠做爲實例標識爲一種特定的類型;構造函數模式賽過工廠模式的地方

把構造函數當函數

- 任何函數,只要經過  new 操做符來調用,那它就能夠做爲構造函數;

//當作構造函數使用

var person = new Person("Simon", 29, "software Engineer");
person.sayName(); //Simon

//普通函數調用
Person("Genf", 23, "DOCTOR");  //添加到window
window.sayName();  // Genf
以剛纔的那種方式定義的構造函數定義在Global對象中(在瀏覽器中是window對象),在全局做用域中調用函數時,this指向的是window對象

// 在另一個對象的做用域中調用
var o = new Object();
Person.call(o, "Kristen", 25, 'nusd');
o.sayName(); // Kristen

構造函數的缺陷

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

person1 和 person2 都有一個名爲 sayName() 的方法;但那兩個方法都不是同一個 Function 的實例,所以會有不一樣的做用域鏈和標識符解析;不一樣實例上的同名函數是不一樣的。

不要忘了,每一個函數都是一個對象!因此sayName方法也能夠這樣寫,所以每一個Person實例都包含着不一樣的Function實例。以這種方式建立函數,會致使不一樣餓做用域和標識符解析。

this.sayName = new Function("alert(this.name)");  //與聲明函數在邏輯上是等價的

咱們能夠檢驗下

alert(person1.sayName() == person2.sayName) //false;

建立兩個完成相同任務的Function 實例沒有必要,何況有this對象在,根本不用在執行代碼前就把函數綁定到特定對象上面。

  • 咱們能夠經過函數定義轉移構造函數外部來解決這個問題。

    function Person(name, age ,job)

    {
        this.name = name;
        this.age = age;
        this.sayName = sayName;
    }
    
    function sayName()
    {
        alert(this.name);
    }
    
    var person1 = new Person("Simon", 29, "software Engineer");
    var person2 = new Person("Zaynex", 29, "DOCTOR");

把sayName()函數的定義轉移到了構造函數外部。

在構造函數內部,將sayName屬性設置成等於全局的 sayName 函數。這樣sayName 包含的是一個指向函數的指針。 person1和person2共享同一個sayName()函數。

但問題是:
在全局做用域中定義的函數實際上只能被某個對象調用,若是對象須要定義不少方法,那麼就要定義多個全局函數。

所以咱們須要用原型模式來解決這個問題。

原型模式

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

咱們沒必要將構造函數定義對象實例的信息中,而是能夠將這些信息直接添加到對象原型中。

function Person(){
    }

    Person.prototype.name ="Simon";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };


    var person1 = new Person();
    person1.sayName();

    var person2 = new Person();
    person2.sayName();

    alert(person1.sayName == person2.sayName);

實際上,person1和person2都不包含屬性和方法, 但能夠調用person1.sayName().這是經過查找對象屬性的過程來實現的。

理解原型對象

不管什麼時候,只要建立了新函數,就會根據一組特定的規則爲該函數建立一個 prototype 屬性,這個屬性指向函數的原型對象。

在默認狀況下,全部原型都會自動得到一個constructor(構造函數)屬性,這個屬性包含在一個指向 prototype屬性所在的函數的指針。舉例說明: Person.prototype.constructor 指向Person.

建立了自定義構造函數以後,其原型對象默認只會取得 constructor 屬性。其餘方法都是從Object繼承來的。

調用構造函數的一個實例後,該實例內部將包含一個指針(ES5中稱爲[[Prototype]],指向構造函數的原型對象。在腳本中沒有標準形式訪問[[Prototype]],但在FF,SF,Chrome中的每一個對象都支持屬性_proto_;在其餘實現中,該屬性對腳本不可見。

要明確的是, 這個連接存在於實例與構造函數的原型對象之間,而非實例與構造函數之間。

雖然在現實中沒法訪問到[[Prototype]],但能夠經過 isPrototypeOf()來肯定是否存在這種關係。
在ES5中新增一個方法,使用 Object.getPrototypeOf()能夠方便的獲取一個對象的原型

每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,
1.先從實例自己開始搜索屬性,存在,搜索結束。若不存在,執行2
2.從實例的原型開始搜索屬性。

繼續剛纔的代碼。若是咱們繼續給實例添加相同的屬性,會怎樣?

function Person(){
    }

    Person.prototype.name ="Simon";
    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 = "xiwenzheng";

    alert(person1.name) //xiwenzheng  ——來自實例
    alert(person2.name) // Simon  ——來自原型

在person1這個實例中重寫屬性,那麼解釋器搜索到了實例自己的屬性直接返回,對於person2而言,實例中沒有屬性,那麼再往實例的原型開始搜素屬性。

  • 給對象添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性,就是阻止咱們訪問原型對象,但並不會修改原型對象中的同名屬性。即便將person1.name 設置爲 null 也不會影響原型對象中的同步屬性。

  • 不過delete 實例屬性,就能夠訪問原型對象中的屬性了。

    function Person(){

    }
    
        Person.prototype.name ="Simon";
        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 = "xiwenzheng";
        alert(person1.name); //xiwenzheng  ——來自實例
        alert(person2.name); // Simon  ——來自原型
        delete person1.name;
        alert(person1.name); // Simon 來自原型

    使用hasOwnProperty()能夠檢測一個屬性是否存在實例中仍是存在原型中,這個方法只在給定屬性存在於對象實例中才會返回 true;

咱們繼續採用剛纔刪除部分的整段代碼。

alert(person1.hasOwnProperty("name")); // 返回false

原先person1.name是存在對象實例中的(被咱們設爲了"Zaynex"),可是被咱們delete了。
若是咱們不delete的話,那就是true了。要想得到原型屬性的描述符,必需要在原型對象上調用 Object.hasOwnPropertydDsecriptor();

原型與 in 操做符

in 操做符會在經過對象可以訪問給定屬性時返回 true ,不論該實行存在於實例中仍是原型中。

  • 利用in:判斷是否有該屬性

  • 利用hasOwnProperty()判斷是否存在對象實例中;

  • 結合之後就能夠判斷該屬性是在原型中仍是在實例中。

    function hasPrototypeProperty(object, name ){

    return !object.hasOwnProperty(name) && (name in object);

    }
    person1.name = "Zaynex";
    alert(hasPrototypeProperty(person1, "name")); //false;存在實例中

    for-in 循環時,返回的都是經過對象訪問的、可枚舉的屬性(即將[[Enumberable]]標記爲true的屬性),在ES5中constructor 和 prototype屬性的 [[Enumberable]]

設爲false,但並非全部瀏覽器都照此實現。
想取得對象上全部可枚舉的實例屬性,可使用Object.Keys()方法。

function Person(){
    }

    Person.prototype.name ="Simon";
    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 = 29;

    var p1keys = Object.keys(p1);
    alert(p1keys);  // name ,age

若是想獲得全部實例屬性,不管是否可枚舉,均可以使用 Object.getOwnPropertyNames()

var keys = Object.keys(Person.prototype);
alert(keys);//  constructor, name ,age, job, sayName

更簡單的原型語法

以前的例子中每添加一個屬性和方法都要 Person.prototype,咱們進行適當的封裝。

function Person(){

}

Person.prototype = {
    name : "Simon",
    age : 29;
    job : "software Engineer",
    sayName : function  () {
        alert(this.name);
    }
};

咱們將 Person.prototype 設置爲等於一個以對象字面量形式建立的新對象。
以前介紹到,每建立一個函數,同時會建立它的prototype對象,這個對象會指定得到constructor 屬性。而咱們在這裏的語法本質上是重寫了默認的 prototype 對象。

因此 constructor屬性也編程了新對象的屬性。(指向Object構造函數),再也不指向Person了。

instanceof 測試 Object 和 Person 都返回 true,但constructor 屬性則等於Object而不等於 Person ;

若是 constructor 的值很重要,則能夠特地設置回適當的值

function Person(){
}

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    sayName : function () {
        alert(this.name);
    }
}
  • 注意,以這種方式重設constructor屬性會致使 [[Enumberable]]特性設置爲true,但默認咱們是不可枚舉constructor屬性的。

爲了兼容ES5的JS引擎,能夠用 Object.defineProperty();

function Person(){
    }

    Person.prototype = {
        name : "Simon",
        job : "software Engineer",
        sayName : function () {
            alert(this.name);
        }
    }
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型的動態性

在原型中找值的過程是一次搜索,所以咱們對原型對象所作的任何修改都能當即從實例中反應出來——即便是先建立實例後修改原型。
不信你看:

var friend = new Person();
Person.prototype.sayHi = function(){
    alert("hi");
};
friend.sayHi();  // "hi"
  • 這個能夠歸結於實例與原型之間的鬆散連接關係。咱們首先會在實例中搜索sayHi的屬性,在沒找到的狀況下會繼續搜索原型,由於實例與原型之間的連接只不過是一個指針。

可是若是重寫整個原型對象,狀況就不同了。調用構造函數時會爲實例添加一個指向最初原型的[[Prototype]]指針,而把原型修改成另一個對象就等於切斷了構造函數與最初原型之間的聯繫。

請記住,實例中的指針僅指向原型,而不指向構造函數

function Person(){

}

var  friend = new Person();

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

friend.sayName();  //error

在這個例子中,咱們建立了Person的實例,而後又重寫了其原型對象,而後在調用sayName()時發生錯誤,所以friend指向的原型不包含以該名字命名的屬性。
重寫整個原型對象

原型對象的問題

  1. 省略了爲構造函數傳遞初始化參數這一環節,結果全部實例在默認狀況下都取得相同的屬性值。

  2. 共享致使的問題,不少屬性能夠共享,對於包含引用類型值的屬性來講,問題比較突出。

    function Person(){

    }
    
    Person.prototype = {
        constructor:Person,
        name : "Simon",
        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 === person2.friends) // true;

修改person1.friends 引用的數組,添加字符串,因爲 friends數組存在 Person.prototype 而非 person1中,因此修改也會形成person2.friends反映出來,若是咱們的初衷就是要共享同一個數組,那無話可說。

但是通常都是要有屬於本身的所有屬性的。而這個問題正是咱們不多看到有人單獨使用原型模式的緣由。

組合使用構造函數模式和原型模式

建立自定義類型的最多見方式就是這個。構造函數用於定義實例屬性,原型模式用於定義方法和共享的屬性。這樣,每一個實例都會有本身的一份實例屬性的副本,但又同事共享着對方法的引用,最大限度節省了內存。此外,這種混成模式還支持向構造函數傳遞參數。

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,Court,Van
   alert(person2.friends); // shelby,Court
   alert(person1.friends === person2.friends); // false
   alert(person1.sayName === person2.sayName); // true

實例屬性都是在構造函數中定義的,全部實例共享的屬性是在 constructor 和方法sayName()是在原型中定義的。

動態原型模式

當其餘OO語言經驗開發人員看到獨立的構造函數和原型時,會感到困惑。所以出現了 動態原型模式———即把全部信息都封裝在了構造函數中,而經過在構造函數中初始化原型(僅在必要的狀況下),又保持了同事使用構造函數和原型的優勢。

換句話說,能夠經過檢查某個應該存在的方法是否有效,來決定是否須要初始化原型。

function Person (name, age, job){
    //屬性
       this.name = name;
       this.age = age;
       this.job = job;
       this.friends = ["Shelby", "Court"];

       if( typeof this.sayName != "function"){
           Person.prototype.sayName = function() {
               alert(this.name);
           };
       }
 }
    // 只有在sayName不存在的狀況下, 纔將其添加到原型中,這段代碼只會在初次調用函數時執行。此後原型已經完成初始化,不須要再作修改。
 var  friends1 = new Person("Nicholas", 29, "software Engineer");
 var friends2 = new Person("Zaynex",19,"Engineer");
 friends1.sayName();
 friends2.sayName();

因爲第一次當friends1初始化以後,friends2就不須要再進行初始化原型。
詳情參考點擊此處

寄生構造函數模式

在上述幾種模式都不適用的狀況下,咱們可使用寄生構造函數模式
基本思想:建立一個函數,該函數的做用僅僅是封裝對象的代碼,而後再返回新建立的對象。

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),或者放置數據被其餘應用程序改動時使用。

穩妥函數與寄生構造函數差別

  1. 新建立的對象的實例方法不引用this。

  2. 不使用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();
    以這種模式建立的對象,除了使用sayName()方法之外,沒有其餘辦法訪問name的值。

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

相關文章
相關標籤/搜索