天天看點

原型、原型對象的了解 及其原型鍊繼承

在 ES5 中,有的人可能對原型,原型對象,及其原型鍊不是很清楚,今天我就說說對這些的深入認識下。(如果有什麼不懂得歡迎留言探讨,當然如果有什麼寫的不恰當的也希望大家留言備注。)

首先,再說原型與原型對象之前,當然有必要清楚構造函數,執行個體,原型與原型對象之間的關系。其實他們的關系也很簡單。

構造函數,執行個體,原型與原型對象之間的關系:

構造函數有它自己的屬性及其方法,其中包括自己定義的屬性和方法外,還有兩個特殊屬性(prototype、constructor);而每個他的執行個體都會擁有它的所有屬性和方法(包括prototype、constructor)constructor則是指向每個執行個體的構造函數,而prototype 原型 則是一個位址指向原型對象,這個原型對象建立了執行個體後,隻會取得constructor屬性,其他的都是從Object繼承而來;在Firefox 、 chrome在對象上都支援一個屬性"proto";這個原型對象的屬性和方法是所有該類執行個體共享的任何該類執行個體夠可以通路該原型對象的屬性和方法(後面會介紹通路原型對象屬性和方法的三個方式)

原型、原型對象的了解 及其原型鍊繼承
原型、原型對象的了解 及其原型鍊繼承

如上圖,p1 ,p2的的執行個體都有Person的屬性和方法,并且prototype都指向原型對象,p1\p2共享prototype原型對象的屬性和方法,各自的constructor都指向Peson,這便是構造函數、執行個體、原型(對象)三者的關系。

現在我來說一說通路原型對象屬性和方法的三個方式:

1.通過Person.prototype 屬性

console.log(Person.prototype.name);//輸出----->person

2.通過 屬性屏蔽 delete (屏蔽構造函數屬性或者方法)

p1.sayName();   //輸出----->構造函數對象

delete p1.name;

console.log(p1.name); //輸出----->原型屬性

delete p1.sayName;

p1.sayName(); //輸出 —>原型對象方法

3.通過Object.getPrototypeOf(p1)

console.log(Object.getPrototypeOf(p1).name);//輸出----->原型屬性

上面我們需要注意就是當執行個體調用屬性或者方法時,有一個”屬性搜尋機制“,所謂”屬性搜尋機制“就是當執行個體通路屬性或者方法時首先會現在自身的執行個體中搜尋,看是否有對應屬性,有,則傳回;如果沒有那麼它會通過prototype 到原型對象中尋找對應的屬性和方法;

原型鍊:

其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。我們知道,每個構造函數都有一個原型對象,每個原型對象都有一個指向構造函數的指針,而執行個體又包涵一個指向原型對象的内部指針。

如果我們讓原型對象(A.prototype) = 另一個類型的執行個體(new B()),那麼,該原型對象(A.prototype)就有一個指向另一個原型對象(B.prototype)的指針,相應的,另一個原型對象(B.prototype)也包含指向另一個構造函數(B)的指針。 ------------->如果另一個的原型(B.prototype)又是另一個類型(C)的執行個體,上訴關系依然成立,就構成了執行個體與原型的鍊條,這就是原型鍊。

實作原型鍊的基本方法:

function Person () {
    this.name = "person";
}
 
Person.prototype.getPersonName = function () {
    return this.name;
};
 
function Student () {
    this.studentname = "student";
}
 
// 繼承了Person
Student.prototype = new Person();
Student.prototype.getStudentName = function () {
    return this.name;
};
 
var stu = new Student();
console.log(stu.getPersonName());           //person
 
           

如上就是通過将 Student()的prototype = new Person() 即子類的原型對象等于父類的執行個體,進而Student.prototype有了Person的所有屬性和方法,實作了繼承。通過實作原型鍊,再結合 “屬性搜尋機制“,則

stu.getPersonName()

會經過三個階段:1.搜尋執行個體 2.搜尋Student.prototype 3.搜尋Person.prototype;如果再沒就會到Object 對象的prototype對象上尋找。因為所有的function和對象等引用類型都繼承Object;這也就說明了為什麼不是通過Object直接執行個體的對象(自定義類型)會有valueof(),toString()等方法。

原型、原型對象的了解 及其原型鍊繼承

需要注意的是,子類有時需要重寫父類的方法或者新增心得方法,這些都要 替換了原型之後(也就是實作繼承之後)。-----------stu 指向Student的原型,Student的原型又指向了Person的原型。進而實作了stu 繼承Student Student繼承Person

function Person () {
   this.name = "person";
}

Person.prototype.getPersonName = function () {
   return this.name;
};

function Student () {
   this.studentname = "student";
}

// 繼承了Person
Student.prototype = new Person();
  // 新增方法
Student.prototype.getStudentName = function () {
   return this.name;
};
  // 重寫父類方法
Student.prototype.getPersonName = function () {
   return false;
};

var stu = new Student();
console.log(stu.getPersonName());           //false

           

還有一點需要注意的是:在通過原型鍊實作繼承時,不能用對象字面量建立原型方法,因為這樣會重寫原型鍊。 剛剛把Person的執行個體指派給原型,緊接着使用字面量導緻出錯。------->因為現在的原型包含的是一個Object的執行個體,不是Person的執行個體,原型鍊被切斷。

function Person () {
    this.name = "person";
}
 
Person.prototype.getPersonName = function () {
    return this.name;
};
 
function Student () {
    this.studentname = "student";
}
 
// 繼承了Person
Student.prototype = new Person();
// 使用字面量添加新方法,會導緻Student.prototype = new Person(); 無效
Student.prototype = {
    getStudentName :function  () {
        return this.name;
    },
    otherMethod :function  () {
        return false;
    }
 
};
 
var stu = new Student();
console.log(stu.getPersonName());           //stu.getPersonName is not a function
  
           

會報錯 stu.getPersonName is not a function 因為此時Student和Person已經沒有關系了。

是以理想的繼承方式是”寄生組合式繼承“,所謂寄生組合式繼承通過借用構造函數來繼承屬性(父類構造函數裡的屬性+方法),通過原型鍊的形式繼承方法(父類原型裡的方法)。

// 寄生組合式繼承(理想的繼承方式)
function inherPrototype (Subobject,Superobject) {
    var prototype = Superobject.prototype;
    prototype.constructor = Subobject;
    Subobject.prototype = prototype;
}
 
 
function Person (name) {
    this.name = name,
    this.hand = ["right-hand","left-hand"],
    this.say = function () {
        alert("hi");
    }
 
}
Person.prototype.sayName = function () {
    alert(this.name);
}
 
function Student (name,age) {
    Person.call(this,name);
    this.age = age;
}
// 實作繼承
inherPrototype(Student,Person);
Student.prototype.sayAge = function () {
    alert(this.age);
}
 
var stu1 = new Student("jack",20);
 
 
// 繼承了屬性(構造函數裡的屬性+方法)
console.log(stu1.hand[0]);                  //----------->輸出right-hand
stu1.say();                         //輸出 hi
//  繼承了原型裡的方法
stu1.sayName();                     //輸出 jack       
  
           

要實作繼承無非就是擁有父類對象的方法和屬性,即擁有一個父類的”模闆副本“,inherPrototype()方法做了如下三件事:1.建立父類的原型(prototype)副本,指派給prototype 2.為建立的prototype副本添加constructor屬性 ,彌補因為原型指派而導緻失去預設的constructor屬性3.把這個副本指派給子類原型。這樣便實作了繼承。

繼續閱讀