基礎二:javascript面向對象、建立對象、原型和繼承總結(上)

前言:本文主要總結一下javascript建立對象的方法、原型、原型鏈和繼承,可是先從建立對象的幾種方法開始,延伸到原型模式建立對象以及其它模式。繼承原本想一塊寫了,發現太多內容了,放到下里總結。javascript

1.建立對象

(1)兩個基本方法

建立對象最基本的兩個方法是:Object構造函數對象字面量java

//Object構造函數方式
    var person = new Object();
    person.name = "Jack";
    person.age = 12;
    person.sayName = function(){
        alert(this.name);
    };
    
    //字面量方式
    var person = {
        name: "Jack",
        age: 14,
        job: "碼農",
        sayName: function(){
            alert(this.name);
        }
    };

(2)工廠模式

上述兩個基本方法的缺點是:使用同一個接口建立不少對象,會產生大量的複製代碼。針對這個缺點,看下面
原理是用函數來封裝以特定接口建立對象的細節數組

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("Jack",15,"碼農");
    var person2 = createPerson("rose",12,"程序媛");

函數createPerson能接收參數構建一個包含全部屬性的對象,而且能夠用不多的代碼不斷的建立多個對象,可是因爲它被函數所封裝,暴露的接口不能有效的識別對象的類型(即你不知道是Object仍是自定義的什麼對象)。瀏覽器

(3)構造函數模式

function Person(name,age,job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            alert(this.name);
        };
    }
    var person1 = new Person("Jack",15,"碼農");  //滿滿的java復古風
    var person2 = new Person("Rose",15,"程序媛");
  1. 與工廠模式相比,構造函數模式用Person()函數代替了createPerson()函數,而且沒有顯示的建立對象,直接把屬性和方法賦值給了this對象。app

  2. 要建立Person的實例,必須使用new關鍵字。函數

  3. person1和person2都是Person的實例,這兩個對象都有一個constructor(構造函數)屬性,該屬性指向Person。 person1.constructor == Person; //truethis

  4. person1便是Person的實例又是Object的實例,後面繼承原型鏈會總結。spa

(3).1構造函數的使用

//當作構造函數使用
     var person1 = new Person("Jack",15,"碼農");
     person1.sayName();   //"Jack"
     
     //當作普通函數使用
     Person("Jack",16,"碼農");     //注意:此處添加到了window
     window.sayName();  //"Jack"
     
     //在另外一個對象的做用域中調用
     var o = new Object();
     Person.call(o,"Jack",12,"碼農");
     o.sayName();   //"Jack"
  1. 第一種當作構造函數使用就很少說了prototype

  2. 當在全局做用域中調用Person("Jack",16,"碼農");時,this對象老是指向Global對象(瀏覽器中是window對象)。所以在執行完這句代碼後,能夠經過window對象來調用sayName()方法,而且返回「Jack」。指針

  3. 最後也可使用call()或者apply()在某個特殊對象的做用域中調用Person()函數

(3).2存在的問題

在(3)構造函數模式的代碼中,對象的方法sayName的功能都同樣,就是alert當前對象的name。當實例化Person以後,每一個實例(person1和person2)都有一個名爲sayName的方法,可是兩個方法不是同一個Function實例。不要忘了,js中函數是對象,因此每一個實例都包含一個不一樣的Function實例,然而建立兩個功能徹底同樣的Function實例是徹底沒有必要的。所以能夠把函數定義轉移到構造函數外。
以下代碼:

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("Jack",15,"碼農");  //滿滿的java復古風
    var person2 = new Person("Rose",15,"程序媛");

可是這樣依然存在問題:

  1. 爲了讓Person的實例化對象共享在全局做用域中定義的同一個sayName()函數,咱們把函數sayName()定義在全局做用域中,並經過指針sayName指向構造函數,因此在全局做用域中的sayName()只能被特定對象調用,全局做用域名不符實,且污染全局變量。

  2. 而且若是對象須要不少種方法,那麼就要定義不少全局函數,對於對象就沒有封裝性,而且污染全局。

2.原型

(1)原型模式建立對象

  1. js不一樣於強類型語言的java,java建立對象的過程是由類(抽象)到類的實例的過程,是一個從抽象到具體的過程。

  2. javascript則不一樣,其用原型建立對象是一個具體到具體的過程,即以一個實際的實例爲藍本(原型),去建立另外一個實例對象。

因此用原型模式建立對象有兩種方式:

1.Object.create()方法
Object.create:它接收兩個參數,第一個是用做新對象原型的對象(即原型),一個是爲新對象定義額外屬性的對象(可選,不經常使用)。

var Person = {
        name:"Jack",
        job:"碼農"
    };
    //傳遞一個參數
    var anotherPerson = Object.create(Person);
    anotherPerson.name     //"Jack"
    //傳遞兩個參數
    var yetPerson = Object.create(Person,{name:{value:"Rose"}});
    yetPerson.name;        //Rose

2.構造函數方法建立對象

  1. 任何一個函數都有一個prototype屬性(是一個指針),指向經過構造函數建立的實例對象原型對象,原型對象可讓全部對象實例共享它所包含的屬性和方法。

  2. 所以沒必要在構造函數中定義對象實例的信息,而是將這些屬性和方法直接添加到原型對象中,從而被實例對象多繼承(繼承後面總結)

//第一步:用構造函數建立一個空對象
    function Person(){
    }
    //第二步:給原型對象設置屬性和方法
    Person.prototype.name = "Jack";
    Person.prototype.age = 20;
    Person.prototype.job = "碼農";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    //第三步:實例化對象後,即可繼承原型對象的方法和屬性
    var person1 = new Person();
    person1.sayName();           //Jack
    
    var person2 = new Person();
    person2.sayName();           //Jack
    
    alert(person1.sayName == person2.sayName);   //true

person1和person2說訪問的是同一組屬性和同一個sayName()函數。

(2)理解原型對象

  1. 只要建立一個函數,就會爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。

  2. 全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。

  3. 當調用構造函數建立一個新的實例對象後,該實例內部會有一個指針([prototype]/_proto_),指向構造函數的原型對象。以下圖:
    圖1

上圖中 :

  1. Person.prototype指向了原型對象,而Person.prototype.construstor又指回了Person。

  2. 注意觀察原型對象,除了包含constructor屬性以外,還包括後來添加的其它屬性,這就是爲何每一個實例化後的對象,雖然都不包含屬性和方法,可是都包含一個內部屬性指向了Person.prototype,能得到原型中的屬性和方法。

(3)判斷一個實例對象的原型

這個方法叫:Object.getPrototypeOf(),以下例子:

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Jack"

  1. 這個方法能夠很方便的取得一個對象的原型

  2. 還能夠利用這個方法取得原型對象中的name屬性的值。

(3)搜索屬性的過程

  1. 當咱們在建立實例化的對象以後,調用這個實例化的對象的屬性時,會前後執行兩次搜索。

  2. 第一次搜索實例person1有name屬性嗎?沒有進行第二次搜索

  3. 第二次搜索person1的原型有name屬性嗎?有就返回。

所以進行一次思考,若是對實例進行屬性重寫和方法覆蓋以後,訪問實例對象的屬性和方法會顯示哪一個?實例對象的仍是對象原型的?

function Person(){
    }
    
    Person.prototype.name = "Jack";
    Person.prototype.age = 20;
    Person.prototype.job = "碼農";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.name = "Rose";
    alert(person1.name);        //Rose
    alert(person2.name);       //Jack
  1. 當爲對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。

  2. 可是這個屬性只會阻止咱們訪問原型中的那個屬性,而不會修改那個屬性
    3.使用delete操做符能夠刪除實例屬性,從而從新訪問原型中的屬性。

function Person(){
    }

    Person.prototype.name = "Jack";
    Person.prototype.age = 20;
    Person.prototype.job = "碼農";
    Person.prototype.sayName = function(){
        alert(this.name);
    };

    var person1 = new Person();
    var person2 = new Person();
    
    person1.name = "Rose";
    alert(person1.name);        //Rose  --來自實例
    alert(person2.name);       //Jack  --來自原型
    
    delete person1.name;
    alert(person1.name);      //Jack  --來自原型

(4)判斷訪問的究竟是對象仍是原型屬性

  1. hasOwnProperty()能夠檢測一個屬性是存在於實例中,仍是原型中,只有在給定屬性存在於對象實例中,纔會返回true。

    person1.hasOwnProperty("name");    //假設name存在於原型,返回false
  2. in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性是存在於實例中仍是原型中

    "name" in person1   //true

因此經過這兩個能夠封裝一個hasPrototypeProperty()函數肯定屬性是否是原型中的屬性。

function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name) && (name in object); 
}

(5)更簡單的原型語法

前面每次添加一個屬性和方法都要寫一次Person.prototype,爲了簡即可以直接這樣

function Person(){
    }
    Person.prototype = {
        name:"Jack",
        age:20,
        job:"碼農",
        sayName:function(){
            alert("this.name");
        }
    };
  1. 上述代碼直接將Person.prototype設置爲等於一個以對象字面量形式建立的新對象

  2. 上述這麼作時:constructor屬性就再也不指向Person了。

  3. 本質上徹底重寫了默認的prototype對象,所以constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數)。

  4. 所以若是constructor值很重要,能夠在Person.prototype中設置回適當的值:
    如上例中能夠添加:constructor:Person,

(6)原型的動態性

咱們對原型對象所作的任何修改都會當即從實例上反映出來-即便先建立實例對象後修改原型也如此

var friend = new Person();
    Person.prototype.sayHi = function(){
        alert("Hi");    
    };
    friend.sayHi();      //"Hi"

儘管能夠隨時爲原型添加屬性和方法,而且修改能當即在實例對象中體現出來,可是若是重寫整個原型對象,就不同了。看下面例子:

function Person(){
    }
    var friend =  new Person();
    Person.prototype = {
        constructor:Person,
        name:"Jack",
        age:20,
        sayName:function(){
            alert(this.name);
        }
    };
    friend.sayName();      //error
  1. 上述代碼先建立了一個Person實例,而後又重寫了其原型對象,在調用friend.sayName()時發生錯誤。

  2. 由於friend指向的原型中不包含以該名字命名的屬性。關係以下圖:
    圖2

(7)原型對象的問題

  1. 省略了爲構造函數初始化參數這一環節,結果是全部實例都取得相同的屬性,但問題不大,能夠爲實例對象重寫屬性來解決。
    2.可是,對於包含引用類型值的屬性來講,問題就比較突出了,由於引用類型中,屬性名只是一個指針,在實例中重寫該屬性並無做用。指針始終指向原來的。

以下例子:

function Person(){}

    Person.prototype = {
        constructor:Person,
        name:"Jack",
        job:"碼農",
        friends:["路人甲","路人乙","路人丙"],
    
    };
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push("路人丁");
    alert(person1.friends);     //["路人甲","路人乙","路人丙","路人丁"]
    alert(person2.friends);    //["路人甲","路人乙","路人丙","路人丁"]
    alert(person1.friends === person2.friends);     //true

上面這個,假如每一個實例對象的引用值屬性不同,則沒法修改。

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

  1. 構造函數模式用於定義實例屬性

  2. 原型模式用於定義方法和共享的屬性

以下代碼:

function Person(name,age,job){
        this.name = name;
        this.job = job;
        this.age = age;
        this.friends = ["路人甲","路人乙"];
    }
    
    Person.prototype = {
        constructor:Person,
        sayName: function(){
            alert(this.name);
        }
    }
    var person1 = new Person("Jack", 20, "碼農");
    var person2 = new Person("Rose", 20, "程序媛");
    
    person1.friends.push("路人丁");
    alert(person1.friends);                  //["路人甲","路人乙","路人丁"]
    alert(person2.friends);                  //["路人甲","路人乙"]
    alert(person1.friends === person2.friends);     //false
    alert(person1.sayName === person2.sayName);    //true

4.寄生構造函數模式

該模式基本思想是建立一個函數,該函數做用僅僅是封裝建立對象的代碼,而後返回新建立的對象。

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("Jack", 16, "碼農");
    friend.sayName();          //Jack
  1. 構造函數在不返回值的狀況下,默認會返回新對象實例。

  2. 經過在構造函數末尾添加一個return語句,能夠重寫調用構造函數時返回的值。

這個方法的用處是:能夠建立一個額外方法的特殊的數組(由於原生對象Array的構造函數不能直接修改)

function SpecialArray(){
            //建立數組
            var values = new Array();
            //添加值
            values.push.apply(values,arguments);
            
            //添加方法
            values.toPipedString = function(){
                return this.join("|");
            };
            //返回數組
            return values;
        }
        var colors = new SpecialArray("black","red","blue");    
        alert(colors.toPipedString());

原本想接着寫繼承的,發現實在太多了,分紅兩篇吧。

相關文章
相關標籤/搜索