Javascript 對象的建立與繼承

建立對象

建立對象的方法

理解原型對象:
不管何時,只要建立了新函數,就會根據一組特定的規則爲該函數建立一個 prototype屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個constructor屬性,這個屬性包含一個指向prototype屬性的所在函數的指針。(重寫原型屬性會默認取消constructor屬性)詳細可見文章下圖。
建立object實例的方式有三種:對象字面量表示法、new操做符跟object構造函數、(ECMAScript5中的)object.create()函數。下面主要講的是最爲複雜的new操做符跟object構造函數的建立對象實例的方法。函數

工廠模式

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("NIcho",29,"software engineer");

工廠模式的問題
工廠模式雖然解決了建立多個類似對象的問題,可是沒有解決對象識別的的問題(即怎樣知道一個對象的類型)。測試

構造函數模式

建立對象的自有屬性的主要方法,每定義一個函數就實例化了一個對象。this

function Person(name,age,job){
        this.name=name;
        this.age=age;
        this.job=job;
        this.sayName=function(){
            alert(this.name);
        }
   }
    //將構造的函數Person實例化,傳入的參數做爲自有屬性,而且繼承自Person.prototype
    var person1=new Person(NIcho",29,"software engineer);
    var person2=new Person(Jhon",26,"software engineer);

Person函數代替了createPerson函數,還有如下不一樣之處:spa

  1. 沒有顯式建立對象
  2. 直接將屬性和方法賦給this對象(這裏的this指的就是這個構造的函數)
  3. 沒有return 語句

咱們在這個例子中建立的對象既是object的實例又是person的實例(全部對象均繼承自object。object能夠理解爲對象的根原型,咱們所用到的全部對象方法的操做,如toString 、substring 、splice等都是繼承自object)。prototype

alert(person1 instanceOf Object);//true
    alert(person1 instanceOf Person);//true

構造函數模式的問題:指針

使用構造函數的主要問題是,每一個方法都要在每一個實例上建立一次。person1和person2都有一個sayName的方法,但兩個方法不是同一個Function實例(ECMAScript中的函數是對象,每定義一個函數就是實例化了一個對象)。

若是沒有自定義向person原型中添加屬性,實例化以後則獲得Person自有屬性的一個副本,而且繼承object(注:person1與person2中sayName()引用位置不一樣)。
圖片描述code

經過把函數定義轉移到構造函數外來解決:對象

//全局函數
    function sayName(){
        alert(this.name);
    }

對應的:繼承

this.sayName:sayName;

原型模式

用於定義實例共享的屬性和方法。索引

function Person(){
    }
    Person.prototype.name="Nicho";
    Person.prototype.age=29;
    Person.prototype.jod="software engineer";
    Person.prototype.sayName=function(){alert(this.name)};
    var person1=new Person();
    person1.sayName();//"Nicho" 繼承Person.prototype
    var person2=new Person();
    person2.sayName();//"Nicho"繼承Person.prototype
    alert(person1.sayName==person2.sayName);//true 引用位置相同

圖片描述

(注:繼承鏈接存在與實例與構造函數的原型之間而不是實例與構造函數之間)

關於原型上的一些函數:

  1. 檢測obj是否爲obj1的原型:obj.isPrototypeOf(obj1)//返回true/false
  2. 得到obj1的原型:object.getPrototypeOf(obj1)
  3. 檢測一個屬性是否存在於實例中(也就是自有屬性)仍是原型中(來自於繼承):
Obj1.hasOwnProperty();//若是obj1有自有屬性則返回true,不然返回false

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

組合使用構造函數模式和原型模式既能夠繼承原型對象的屬性,又能夠設置自由屬性。

function Person(name,age,job){
            this.name=name;
            this.age=age;
            this.job=job;
    }
    Person.prototype={
        constructor:Person,
        sayName:function(){
            alert("Hi");
        }
     }
    var person1=new Person(「Nicho」,29,」software engineer」);
    //person1自有屬性:name aga job;原型屬性(來自繼承):constructor sayName

代碼讀取某個對象的某個屬性時的搜索方法

搜索首先從對象實例開始,若是實例中找到了給定名字的屬性,則返回該屬性的值。若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找給定名字的屬性。若是在原型對象中找到了該屬性,則返回屬性的值,不然一直向上查找給定名字的屬性,直到object。若是沒有找到則返回undefined。

這就須要咱們注意:雖然咱們能夠經過對象實例訪問原型中的值,但卻不能經過對象實例(注意!!這裏說的是經過對象實例而不是在原型中修改屬性)來重寫原型中的值。若是在實例中添加了一個與實例原型中同名的屬性,該屬性將會屏蔽原型中的那個屬性。
來看下面的的例子:

function Person(){
    
    }
    Person.prototype.name="Nicho";
    Person.prototype.age=29;
    Person.prototype.jod="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);//Nicho-來自原型;
    //person1對屬性 name的修改並不能修改原型上的相應屬性,所以person2繼承自原型

圖片描述

  1. person1=new Person()person3=person1是不一樣的:前者是實例化一個對象,後者遵循複製函數(對象引用)的原理
  2. 對象總體進行操做(引用)和對對象中的某個屬性(值)進行操做實現原理是不一樣的
  • 整個對象的複製即引用相同
var person3=person1;//整個對象的複製即引用相同(指針指針指針!只是一個索引,實際存儲都不在指針位置下)
    person3.name=」newName」;//修改屬性名稱的值
    alert(person1.name);//newName;//另外一個來自此處的引用屬性的值會修改;即複製對象原理
    alert(person1.isPrototypeOf(person3));//false
    alert(Person.prototype.isPrototypeOf(person3));//true
  • 簡單的屬性的複製,並不是引用同一個屬性值.
var person1=new Person("NIcho",29,"software engineer");
      var person2=new Person("HIcho",9,"Hardware engineer");
      person1.name=person2.name;//簡單的屬性的複製,並不是引用同一個屬性值
      person2.name="NEW";//嘗試賦新值
      alert(person1.name);//HIcho,說明是值的傳遞而並不是引用

繼承

  1. ECMAScript只支持實現繼承,並且實現繼承主要是依靠原型鏈來實現的。
  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;
        };
        var instance = new subType();
        alert((instance.getSuperValue)());//true  getSuperValue來自superType.prototype
        alert(instance.property);//true   property來自superType實例

實現的本質是重寫原型對象,代之以一個新類型的實例。換句話說,原來存在於superType的實例的全部屬性和方法(包括自有屬性和繼承屬性),如今也存在於subType.prototype中了。
圖片描述
(此時instance.constructor如今指向的是superType,由於subType的原型指向了另外一個對象superType的原型,而這個原型對象的constructor指向的是superType)

注意:

  1. 別忘記默認的對象Object.prototype: subType繼承了superType,superType繼承了Object.prototype
  2. 肯定原型和實例的關係:
  • instanceOf;測試實例與原型鏈中出現過的構造函數
alert(instance instanceOf  superType);//true
   alert(instance instanceOf  subType);//true
  • isPrototypeOf:只要原型鏈中出現過的原型均可以說是該原型鏈所派生出來的實例的原型
alert(superType.protoType.isPrototypeOf (instance));//true
    alert(subType.protoType.isPrototypeOf (instance));//true

原型鏈的問題:
在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而,原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了。

解決原型鏈繼承帶來的問題的方法

解決這個問題的技術是借用構造函數,使用最多的繼承模式是組合繼承。此外還有原型式繼承,寄生式繼承,寄生組合繼承。這裏主要講借用構造函數和組合繼承。

借用構造函數

在子類型構造函數的內部調用超類型的構造函數。

function superType(){
        this.colors=["red","blue","green"];
      }
      function subType(){
        //繼承了superType
        superType.call(this);//在新建立的subType實例的環境下調用superType函數
        //這樣一來,就會在新subType對象上執行superType函數中定義的全部初始化代碼
        //結果subType的每個實例都會具備本身的color屬性的副本了
      }
      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;
  this.colors = ["red","blue","green"];
}

superType.prototype.sayName=function(){
  console.log(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(){
  console.log(this.age);
}

var instance1 = new subType("Nicho",29);
instance1.sayName();//」Nicho」
instance1.sayAge();//29

總結:

  1. subType的自有屬性: this.age = age
  2. subType的繼承屬性:
superType.prptotype.sayName
subtype.prototype.sayAge
function superType(name){    
    this.name=name;
       this.colors=["red","blue","green"];
     }

組合繼承改進

組合式繼承的問題
組合式繼承是js最經常使用的繼承模式,但組合繼承的超類型在使用過程當中會被調用兩次;一次是建立子類型的時候,另外一次是在子類型構造函數的內部。咱們使用Object.create作一下改進:

function superType(name){
  this.name = name;
  this.colors = ["red","blue","green"];
}

superType.prototype.sayName=function(){
  console.log(this.name);
}

function subType(name,age){
  superType.call(this,name);
  this.age = age;
}

function inheritPrototype(sup,sub){
  const obj = Object.create(sup);
  obj.constructor = sub;
  sub.prototype = obj;
}
// 繼承
inheritPrototype(superType,subType);

重寫原型對象

下面讓咱們看下一種狀況:重寫原型對象(徹底重寫而不是簡單的添加原型對象的屬性)
咱們知道,在調用構造函數是會爲實例添加一個prototype指針,而把這個原型修改成另外一個對象就等於切斷了構造函數與最初原型之間的聯繫。請記住:實例中的指針僅指向原型而不指向構造函數。

function Person(){};
    var friend=new Person();
    Person.prototype={
    Constructor:Person,
    Name:「Nicholas」,
    Age:29,
    sayName=function(){
    alert(「this.name」);
    }
    }
    friend.sayName();//error

重寫原型對象以前:

圖片描述

重寫原型對象以後:

圖片描述

由圖可見:重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫,它們引用的還是最初的原型。

相關文章
相關標籤/搜索