原來你是這樣的---原型和原型鏈

  把JS的原型和原型鏈從新梳理了一遍,而後動手繪製了一張流程圖,原型和原型鏈的祕密就藏在這張圖上。繪製流程圖的好處就是在繪製的過程當中,既檢驗本身對這個知識點的掌握情況,同時在繪製過程當中會對這個知識點印象更深入,理解更透徹,建議每一個感興趣的小夥都來身體力行一次。ide

  爲了更清晰的瞭解原型鏈的走向,先建立三個構造函數,創建多重繼承關係,分別爲Person、ChinaPerson、ProvincePerson,它們之間的繼承關係爲:ProvincePerson 繼承了 ChinaPerson, ChinaPerson繼承了Person。接下來,咱們函數

  1. 先貼上這份多重繼承的代碼;
  2. 繪製流程圖,根據流程圖剖析原型鏈的祕密;
  3. 寫些測試代碼驗證;

  先貼上代碼,每一個子構造函數會在繼承父級的基礎上,分別在構造函數裏面和原型裏面,自定義添加本身的屬性和方法;另外在Person原型上寫上和構造函數裏面同名的屬性和方法,用來驗證同名方法名時,構造函數裏面的方法和原型上的方法哪一個優先執行;在ProvincePerson上會重寫從父級繼承的方法,側面粗略展現下面向對象的多態特性。測試

/**
 * JavaScript的多級繼承和多態、原型和原型鏈的體現
 * */
//1.1 構造函數:人(Person)
function Person(name){
    this.name = name ? name : "人類";
    this.methodPerson = function(){
        console.log("Person構造函數裏面的方法methodPerson-->個人標籤:" + this.name);
    }

    console.log("********Person 構造函數 初始化********");
}
//給Person的原型上添加屬性和方法
Person.prototype.age = 18;
Person.prototype.run = function(){
    console.log("Person原型方法run-->name: " + this.name + ", age: " + this.age + ", 歡快的run");
}
//問題:若是構造函數的原型裏面的屬性和方法,和構造函數的屬性和方法同名,實例對象調用屬性和方法時執行哪一個?
//Person原型的name和Person構造函數的name,實例對象調用哪一個?
Person.prototype.name = "炎黃子孫";
Person.prototype.methodPerson = function(){
    console.log("Person原型的methodPerson方法-->標籤:" + this.name + ", age: " + this.age);
}


//1.2 構造函數:中國人(ChinaPerson),繼承於Person
function ChinaPerson(name, skin){
    Person.call(this, name); //調用父級Person構造函數

    this.skin = skin ? skin : "黃色";
    this.methodChinaPerson = function(){
        console.log("ChinaPerson構造函數裏面的方法methodChinaPerson-->膚色:" + this.skin + ", 標籤: " + this.name);
    }

    console.log("********ChinaPerson 構造函數 初始化********");
}
//設置ChinaPerson原型指向Person原型,至關於ChinaPerson繼承Person
ChinaPerson.prototype = Object.create(Person.prototype);
//設置新原型的構造函數指向本身
ChinaPerson.prototype.constructor = ChinaPerson;

//給ChinaPerson的原型自定義添加屬性和方法
ChinaPerson.prototype.hero = "譚嗣同";
ChinaPerson.prototype.write = function(){
    console.log("ChinaPerson原型裏面的方法write-->我自橫刀向天笑,去留肝膽兩崑崙!is who? " + this.hero + ", 標籤: " + this.name + ", skin: " + this.skin);
}


//1.3 構造函數:ProvincePerson, 繼承於ChinaPerson
function ProvincePerson(name, skin, count){
    ChinaPerson.call(this, name, skin);  //調用父級ChinaPerson構造函數

    this.count = count ? count : 8000;  
    this.methodProvincePerson = function(){
        console.log("ProvincePerson構造函數裏面的方法methodProvincePerson-->數量:" + this.count + "萬, 膚色:"+ this.skin + ", 標籤:" + this.name);
    }
    //重寫從父級繼承下來的方法methodChinaPerson
    this.methodChinaPerson = function(){
        console.log("ProvincePerson構造函數裏面重寫父級方法methodChinaPerson....");
    }
    console.log("********ProvincePerson 構造函數 初始化********")
}
//設置ProvincePerson原型指向ChinaPerson原型,至關於構造函數ProvincePerson繼承於ChinaPerson
ProvincePerson.prototype = Object.create(ChinaPerson.prototype);
//設置新原型的構造函數指向本身
ProvincePerson.prototype.constructor = ProvincePerson;
//給ProvincePerson的原型上自定義添加屬性和方法
ProvincePerson.prototype.feature = "長沙臭豆腐";
ProvincePerson.prototype.eat = function(){
    console.log("ProvincePerson原型裏面的方法eat-->標籤:" + this.name + ", 地方小吃是:" + this.feature + ", hero: " + this.hero + ", skin: " + this.skin);
}
//重寫從父級原型繼承下來的方法
ProvincePerson.prototype.write = function(){
    console.log("ProvincePerson原型裏面重寫從父級原型繼承的write方法-->。。。。^_^");
}
View Code

 

  結合以上代碼,繪製構造函數原型鏈關係,以下圖:this

  

對上圖進行說明,和原型鏈知識點進行概括:spa

  • ProvincePerson、ChinaPerson、Person三個是自定義構造函數,Function、Object兩個是系統構造函數;
  • 原型鏈方向(尋找父級方向)爲:ProvincePerson --> ChinaPerson --> Person --> Object --> null ;   Function -->  Object --> null ;
  • 上圖中obj是構造函數ProvincePerson的實例對象;矩形表明構造函數六邊形表明構造函數的原型對象紅色虛線表明實例對象經過其私有原型屬性__proto__尋找父級原型走向;
  • prototype是構造函數的屬性,__proto__是構造函數的實例對象的屬性
    • 實例對象的__proto__屬性指向該對象的構造函數的prototype屬性,即 實例對象.__proto__ = 構造函數.prototype ; 
    • __proto__是隱式原型,日常不建議直接使用,一般用Object.getPrototypeOf(對象)來獲取實例對象的原型;
    • 構造函數的prototype 和 實例對象的__proto__ 都是對象
  • 敲重點了:
    • 函數調用時,使用new關鍵字,叫構造函數,好比var obj = new ProvincePerson() 。這時ProvincePerson叫構造函數,是一個類模板;
    • 當函數不使用new關鍵字,直接調用時,就是一個普通函數,好比ProvincePerson()、Object()、Function() 等等,這樣使用它們都叫普通函數;
    • 全部的普通函數都是構造函數Function的實例對象,好比Object、Function做爲普通函數調用時它們都是Function的實例對象。
    • 這就是爲何函數既有prototype屬性,也有__proto__屬性,由於它們都有雙重身份:
    • 第一重身份是它們有可能會使用new關鍵字,這時它們是構造函數,有prototype屬性;
    • 第二重身份是它們不使用new關鍵字,直接調用,這時候它們都是構造函數Function的實例對象,因此這時候它們有__proto__屬性。
    • Function做爲一個特殊的存在,特殊之處在於 Function.prototype = Function.__proto__ ,即它做爲構造函數的原型(prototype) 和 它做爲普通函數的實例對象的原型(__proto__) 指向同一個對象;
  • 構造函數的原型的constructor屬性指向構造函數,實例對象的constructor也指向構造函數,即 構造函數.prototype.constructor = 構造函數  = 該構造函數的實例對象.constructor 
  • 一個構造函數繼承自父級構造函數,會擁有父級全部對外的,包括構造函數的屬性和方方法,和父級原型的屬性和方法;
  • 子級構造函數能夠對繼承的屬性和方法進行重寫;若是構造函數裏面的方法或屬性,和它的原型上的方法或屬性同名,則調用時優先構造函數裏面的方法或屬性;
  • 全部的對象或構造函數經過原型鏈,追本溯源,最後的老祖宗都是Object。即全部的構造函數都是Object的子級或間接子級。Object的原型的原型是null,到這裏就是終極大結局了!

 

  大概知識點就是這些,在上面代碼的基礎上,再來一些測試代碼,驗證一下。先上一份測試原型鏈關係的代碼:prototype

//測試一下
var pro1 = Person.prototype, pro2 = ChinaPerson.prototype, pro3 = ProvincePerson.prototype;
//ProvincePerson原型的原型 === ChinaPerson的原型
var pro3_china = Object.getPrototypeOf(pro3);

//ProvincePerson原型的原型的原型 === Person的原型
var pro3_person = Object.getPrototypeOf(pro3_china);

//ProvincePerson原型的原型的原型的原型 === Object的原型
var pro3_object = Object.getPrototypeOf(pro3_person);

//Function和Object做爲普通函數時是構造函數Function的實例對象,獲取這兩個實例對象的原型
var pro_function = Object.getPrototypeOf(Function), pro_object = Object.getPrototypeOf(Object);

console.log("************* 原型鏈測試 start **********")
console.log("構造函數ProvincePerson繼承自ChinaPerson, 構造函數ChinaPerson繼承自Person, Person繼承自Object")
console.log("Object --> Person --> ChinaPerson --> ProvincePerson")
console.log("Person.原型:", pro1);
console.log("ChinaPerson.原型:", pro2);
console.log("ProvincePerson.原型:", pro3);
console.log("ProvincePerson.原型.原型: ", pro3_china);
console.log("ProvincePerson.原型.原型.原型: ", pro3_person);
console.log("ProvincePerson.原型.原型.原型.原型:", pro3_object);
console.log("ProvincePerson.原型.原型 === ChinaPerson.原型 --> ", pro3_china === pro2);
console.log("ProvincePerson.原型.原型.原型 === Person.原型 --> ", pro3_person === pro1);
console.log("ProvincePerson.原型.原型.原型.原型 === Object.原型 --> ", pro3_object === Object.prototype);
console.log("Function.prototype === Function.__proto__ --> ", Function.prototype === pro_function);
console.log("Object.__proto__ === Function.prototype --> ", pro_object === Function.prototype);
console.log("************ 測試 end ************\n")

/*
測試結果:

************* 原型鏈測試 start **********
構造函數ProvincePerson繼承自ChinaPerson, 構造函數ChinaPerson繼承自Person, Person繼承自Object
Object --> Person --> ChinaPerson --> ProvincePerson
Person.原型: {age: 18, run: ƒ, name: "炎黃子孫", methodPerson: ƒ, constructor: ƒ}
ChinaPerson.原型: Person {constructor: ƒ, hero: "譚嗣同", write: ƒ}
ProvincePerson.原型: ChinaPerson {constructor: ƒ, feature: "長沙臭豆腐", eat: ƒ, write: ƒ}
ProvincePerson.原型.原型:  Person {constructor: ƒ, hero: "譚嗣同", write: ƒ}
ProvincePerson.原型.原型.原型:  {age: 18, run: ƒ, name: "炎黃子孫", methodPerson: ƒ, constructor: ƒ}
ProvincePerson.原型.原型.原型.原型: {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
ProvincePerson.原型.原型 === ChinaPerson.原型 -->  true
ProvincePerson.原型.原型.原型 === Person.原型 -->  true
ProvincePerson.原型.原型.原型.原型 === Object.原型 -->  true
Function.prototype === Function.__proto__ -->  true
Object.__proto__ === Function.prototype -->  true
************ 測試 end ************
*/
View Code

   測試結果截圖:3d

 

  再來一份對於多級繼承和重寫展現的測試代碼:日誌

//第二波測試,測試構造函數的繼承 和 多態(重寫從父級繼承下來的屬性或方法)
console.log("\n************* 繼承和重寫 start ************");
console.log(">>>>>>準備建立一個Person實例對象>>>>>");
var per = new Person("王大錘");  
per.methodPerson();
per.run();
console.log("*****Person實例對象測試結論:構造函數和原型有同名屬性或方法,實例對象優先調用構造函數的屬性或方法*****\n");

console.log("\n>>>>>準備建立一個ChinaPerson實例對象,ChinaPerson繼承了Person >>>>>");
var chObj = new ChinaPerson("中國人", "黃色");
chObj.methodChinaPerson();
chObj.write();
chObj.methodPerson();
chObj.run();
console.log("*****ChinaPerson實例對象測試結論:繼承自父類Person, 擁有父類全部對外的構造函數裏面和原型裏面的屬性和方法\n");

console.log("\n>>>>>準備建立一個ProvincePerson實例對象,ProvincePerson繼承了ChinaPerson>>>>>");
var proObj = new ProvincePerson("湖南人", "黃色", 888);
proObj.methodProvincePerson();
proObj.eat();
proObj.methodChinaPerson();
proObj.write();
proObj.methodPerson();
proObj.run();
console.log("*****ProvincePerson實例對象測試結論:擁有父級和父級的父級的全部對外的,包括構造函數裏面和原型裏面的屬性和方法;另外也能夠對父級屬性或方法進行重寫");
console.log("************  測試 end ************\n");

/*
測試結果打印日誌:

************* 繼承和重寫 start ************
>>>>>>準備建立一個Person實例對象>>>>>
********Person 構造函數 初始化********
Person構造函數裏面的方法methodPerson-->個人標籤:王大錘
Person原型方法run-->name: 王大錘, age: 18, 歡快的run
*****Person實例對象測試結論:構造函數和原型有同名屬性或方法,實例對象優先調用構造函數的屬性或方法*****

>>>>>準備建立一個ChinaPerson實例對象,ChinaPerson繼承了Person >>>>>
********Person 構造函數 初始化********
********ChinaPerson 構造函數 初始化********
ChinaPerson構造函數裏面的方法methodChinaPerson-->膚色:黃色, 標籤: 中國人
ChinaPerson原型裏面的方法write-->我自橫刀向天笑,去留肝膽兩崑崙!is who? 譚嗣同, 標籤: 中國人, skin: 黃色
Person構造函數裏面的方法methodPerson-->個人標籤:中國人
Person原型方法run-->name: 中國人, age: 18, 歡快的run
*****ChinaPerson實例對象測試結論:繼承自父類Person, 擁有父類全部對外的構造函數裏面和原型裏面的屬性和方法

>>>>>準備建立一個ProvincePerson實例對象,ProvincePerson繼承了ChinaPerson>>>>>
********Person 構造函數 初始化********
********ChinaPerson 構造函數 初始化********
********ProvincePerson 構造函數 初始化********
ProvincePerson構造函數裏面的方法methodProvincePerson-->數量:888萬, 膚色:黃色, 標籤:湖南人
ProvincePerson原型裏面的方法eat-->標籤:湖南人, 地方小吃是:長沙臭豆腐, hero: 譚嗣同, skin: 黃色
ProvincePerson構造函數裏面重寫父級方法methodChinaPerson....
ProvincePerson原型裏面重寫從父級原型繼承的write方法-->。。。。^_^
Person構造函數裏面的方法methodPerson-->個人標籤:湖南人
Person原型方法run-->name: 湖南人, age: 18, 歡快的run
*****ProvincePerson實例對象測試結論:擁有父級和父級的父級的全部對外的,包括構造函數裏面和原型裏面的屬性和方法;另外也能夠對父級屬性或方法進行重寫
************  測試 end ************
*/
View Code
相關文章
相關標籤/搜索