從js中物件的建立方式和繼承,談談對原型鏈的理解

不吃藥不喝水發表於2019-08-04

在理解每一個概念的時候,我習慣性的會從以下角度去思考:

  • 這個概念為什麼會被提出,它的提出背景是什麼?
  • 這個概念是什麼,它解決了什麼問題?
  • 這個概念如何解決相關問題?

同樣在理解原型鏈的過程中,我也進行過相關思考,現對自己的理解做出總結。

理解原型鏈之前,最好先理解清楚以下兩個概念:js中物件的建立方式繼承

一、物件的建立方式

物件字面量

var stu = {
    name:'andy', 
    gender:'female',
    showInfo:function(){
        console.log('common function');
    }
};
複製程式碼

使用字面量方式直接建立物件,雖然直觀明瞭,但是當需要建立10個、100個、甚至1000個同一型別的物件的時候,這種方式的弊端就顯而易見。我們不應為了實現需求而真的複製貼上1000遍相同結構的程式碼,那麼我們應該怎麼做呢?這個時候,大家比較容易想到的就是函式,下面就介紹使用函式建立物件的方式--工廠方式。

工廠方式

function createStudent(name,gender){
    return {
        name:name,
        gender:gender,
        showInfo:function(){
            console.log('common function');
        }
    }
}
var stu = createStudent('andy','female');
複製程式碼

使用工廠函式這種方式,如果要建立10000個物件,只需要呼叫1000次函式即可。但是,每個物件都擁有公共方法showInfo(),這顯然是不合理的。如何讓每個物件都保有自己獨特屬性值的同時,共享同一個公共方法呢?

Object.create()方式

Object.create()以一個物件為原型,建立新物件的方式。

var A = {
    name:'andy',
    gender:'female',
    showInfo:function(){
        console.log('common function');
    }
};
var B = Object.create(A);//B={}
var C = Object.create(A);//C={}
複製程式碼

B和C都是以A物件為原型建立的新物件,並且剛建立出來都是空物件。這其中的原理又是什麼呢?A可以看作是共享池,這個池子中放著很多共享的屬性和方法,B和C通過某種“指標”__proto__指向A,這樣B和C就“擁有”共享池中的方法和屬性了。但是,這種方式也存在一定的弊端:

  • 因為A是共享的,那麼B和C就可以通過__proto__“指標”修改A中的屬性值和方法;
  • B和C如果想要在不改變A屬性的情況下,擁有特有的屬性值,必須要進行以下操作:
    B.name = 'bob';
    B.gender = 'male';
    C.name = 'cindy';
    C.gender = 'female';
複製程式碼

使用Object.Create()方法建立1000個不同屬性值的物件,雖然實現了方法的共享,但是也存在一些隱患。那麼,有沒有其他方式呢?

建構函式

function Student(name,gender){
    this.name = name;
    this.gender = gender;
}
Student.prototype.showInfo = function (){
    console.log('common function');
}
var andy = new Student('andy','female');
andy.showInfo();//common function
var bob = new Student('bob','male');
bob.showInfo();//common function
複製程式碼

使用建構函式的方式,如果要建立1000個物件,只需要 new 建構函式(引數) 1000次即可。它和工廠方式的區別是,同樣建立1000個物件,使用建構函式方式時showInfo()在記憶體中佔有的容量僅是工廠方式的1/1000。而這裡new的本質是什麼呢?這裡簡要總結一下:

1. 建立一個空物件{};//var obj = {};
2. 將這個空物件的原型,指向其建構函式的prototype物件;//obj.__proto__ = Object.create(建構函式.prototype)
3. 繫結this關鍵字到當前空物件;
4. 執行建構函式內部的程式碼,執行完畢返回物件例項。
複製程式碼

但是有的建構函式和建構函式之間存在某種關係(比如學生包括小學生、中學生、大學生),而這個關係可以用“繼承”來描述。

二、JS中的繼承

假如,我們需要建立這樣一種物件:

function MidStudent(name,gender,school){
    this.name = name;
    this.gender = gender;
    this.school = school;
}
MidStudent.prototype.showInfo = function (){
    console.log('common function');
}

var cindy = new MidStudent('cindy','female','一中');
cindy.showInfo();//common function
var elle = new MidStudent('elle','female','十四中');
elle.showInfo();//common function
複製程式碼

我們可以發現MidStudent和Student比起來只是多了一個屬性school,如果重新定義MidStudent,實際上也是浪費空間的。那麼有什麼方式可以更節省空間呢?可以想到的方式是繼承。那麼,js又是如何實現繼承的呢?

function MidStudent(name,gender,school){
    Student.call(this,name,gender);//改變this指向
    this.school = school;
}
MidStudent.prototype = Object.create(Student.prototype);//以Student.prototype物件為原型,建立MidStudent.prototype物件
MidStudent.prototype.constructor = MidStudent;//讓MidStudent的constructor指向自身

var cindy = new MidStudent('cindy','female','一中');
cindy.showInfo();//common function
var elle = new MidStudent('elle','female','十四中');
elle.showInfo();//common function
複製程式碼

關於使用call函式改變this指向,這裡的原理不再展開介紹。

三、原型鏈

基於以上對物件的建立方式繼承的討論,下面開始對原型鏈的探討。在使用建構函式Student建立andy和bob物件的時候,這兩個物件中並沒有定義showInfo方法,那麼它們是如何實現對showInfo方法的呼叫的呢?

建構函式建立物件
在js中,所有物件都有__proto__屬性,而函式除此之外還有prototype屬性。回到剛剛的問題,例項物件andy和bob是如何實現對showInfo方法方法的呼叫的呢?

從js中物件的建立方式和繼承,談談對原型鏈的理解
結合上圖,andy.showInfo() 方法的呼叫過程如下:

  1. 首先查詢自己內部是否有此方法,如果有直接呼叫,如果沒有,通過andy.__proto__向上查詢至Student.prototype;
  2. 查詢Student.prototype內部是否有此方法,如果有直接呼叫,如果沒有,通過Student.prototype.__proto__向上查詢至Object.prototype;
  3. 查詢Object.prototype內部是否有此方法,如果有直接呼叫,如果沒有,通過Object.prototype.__proto__向上查詢至null,結束。

整個查詢過程就是如下的一個原型鏈的鏈式查詢過程:

鏈式

總結下來,原型鏈描述的是例項物件和建構函式之間的一種關係,這種關係就是通過物件的__proto__屬性和函式的prototype屬性進行連結。 回到最開始討論的問題,

  • 這個概念為什麼會被提出,它的提出背景是什麼?

      原型鏈主要就是為了在建立物件時更節省記憶體空間,物件可以通過鏈式查詢的過程去共享屬性和方法的定義。
    複製程式碼
  • 這個概念是什麼,它解決了什麼問題?

      原型鏈描述的是例項物件和建構函式之間的一種關係,可以解決物件間不用重複定義相同方法和屬性的問題。
    複製程式碼
  • 這個概念如何解決相關問題?

      通過物件的__proto__屬性和函式的prototype屬性進行連結,不斷向上查詢的過程。
    複製程式碼
關於本文的不合理之處,請各位不吝賜教,悉數指出。

相關文章