1. 類
ES6 中新增加了類的概念,可以使用 class 關鍵字宣告一個類,之後用這個類來例項化物件。即類的用途:例項化物件。
// 建立一個Person類 class Person { } // 建立一個Person類的例項物件 const p1 = new Person() console.log(p1)
列印結果如下:
注意,輸出的p1是一個例項物件,而不是類!這裡的輸出結果有 Person,是為了說明這個例項物件是由誰new出來的,藍框表示輸出的確實是一個例項物件。
思考:為什麼前面要帶一個類呢?假設還有一個Dog類,同樣new一個Dog類的例項物件p2,這時候必須通過類名來區分例項物件,否則就人畜不分了......
現在我們瞭解了類和例項物件,下面我們再來研究建構函式。
2. 建構函式
一般對例項物件都會有一些初始化操作,比如人有姓名和年齡,因此就出現了類的構造器方法constructor(),它的作用是給例項物件新增屬性,語法如下:
//步驟1 在類中定義建構函式constructor,函式名固定 class Person { constructor(name,age) {//定義形參 this.name = name;//將形參賦值給this物件的對應屬性 this.age = age; } } //步驟2 在例項化物件的時候,傳遞實參 const p1 = new Person('bahg', 18); //這裡的實參預設傳遞給Person類中的constructor console.log(p1.name);//bahg
思考:
1、建構函式必須寫嗎?答:不是必須的,但是對於Person這個類而言,它沒有繼承任何類,如果不寫構造方法也就沒有任何意義。下面會講到繼承類,它可以不寫建構函式,預設會呼叫父類的建構函式。
2、建構函式中的 this 是什麼?答:是例項化物件,也就是p1
一個類除了有構造方法,還有一般方法,一般方法是用來定義行為的,比如人可以吃飯睡覺敲程式碼,這裡我們定義一個speak方法:
// 建立一個Person類 class Person { constructor(name, age) { this.name = name this.age = age } speak() { console.log(`名字為${this.name}的人年齡為${this.age}`) } } // 建立一個Person類的例項物件 const p1 = new Person('bahg', 18) const p2 = new Person('zzz', 21) console.log(p1) console.log(p2) p1.speak() p2.speak()
列印結果如下:
3. 原型物件
在JavaScript中,每當定義一個函式資料型別(普通函式、類)時候,都會天生自帶一個prototype
屬性,這個屬性指向函式的原型物件,並且這個屬性是一個物件資料型別的值。
讓我們用一張圖表示建構函式和例項原型之間的關係:
原型物件就相當於一個公共的區域,所有同一個類的例項都可以訪問到這個原型物件,我們可以將物件中共有的內容,統一設定到原型物件中。
思考:
1、p1和p2為什麼沒有出現speak方法呢?被放到了哪裡?答:類的原型物件上,如下所示:
2、speak方法是給誰用的?答:給例項物件用的
3、speak的this指向誰?答:指向它的最後呼叫者。注意:“誰呼叫它就是誰”這種說法是不準確的,因為call、apply、bind都可以更改函式中的this指向,例如 p1.speak.call({a:1,b:2}) 此時this就是undefined。
4. 繼承
繼承是為了複用程式碼,下面我們再定義一個Student類,它繼承於Person類:
這裡我們沒有在Student類中寫構造方法,但是仍然可以列印s1,說明構造器方法不是非寫不可。
思考:我們什麼時候需要寫構造器方法呢?
答:子類有自己特有的屬性時,比如學生有年級這個屬性,此時就需要重新寫自己的構造器方法,但是注意:一旦寫了構造器,就必須呼叫 super() 方法,並且要寫在其他屬性之前!super函式的作用是呼叫父類的構造器。
class Student extends Person { constructor(name, age, grade) { super(name, age) this.grade = grade } } const s1 = new Student('小明', 15, '高一')
console.log(s1)
思考:此時Student的原型物件上有方法嗎?
答:除了構造器方法之外沒有其它方法,因為Student類裡面沒有寫自己的方法。
那麼問題來了,學生能夠說話嗎?答案是肯定的,通過s1.speak()可以列印出結果,那麼speak方法在哪呢?這裡就要引出原型鏈了。
5.原型鏈
在JavaScript中萬物都是物件,物件和物件之間也有關係,並不是孤立存在的。物件之間的繼承關係,在JavaScript中是通過prototype物件指向父類物件,直到指向Object物件為止,這樣就形成了一個原型指向的鏈條,專業術語稱之為原型鏈。
舉例說明:person → Person → Object ,普通人繼承人類,人類繼承物件類
當我們訪問物件的一個屬性或方法時,它會先在物件自身中尋找,如果有則直接使用,如果沒有則會去原型物件中尋找,如果找到則直接使用。如果沒有則去原型的原型中尋找,直到找到Object物件的原型,Object物件的原型沒有原型,如果在Object原型中依然沒有找到,則返回undefined。
對於例項物件s1,它在呼叫speak方法時,會先去原型物件上查詢,發現沒有這個方法,就繼續去原型鏈上查詢,找到了父類原型物件上的speak方法,如圖所示:
從始至終都只有一個speak,沿著原型鏈一層層去找。我們還可以對speak方法進行重寫,什麼時候需要重寫呢?當子類要對父類方法進行擴充套件時,就可以重寫方法。
class Student extends Person { constructor(name, age, grade) { super(name, age) this.grade = grade } speak() { console.log(`名字為${this.name}的人年齡為${this.age},讀${this.grade}`) } } const s1 = new Student('小明', 15, '高一') console.log(s1)
思考:此時Student原型物件上有speak方法嗎?答:有,s1列印結果如下:
按照原型鏈查詢規則,當它查詢到藍色箭頭的時候就直接呼叫speak函式了,不會再往下查詢。
假設現在學生類還有自己獨有的study方法,思考:
1、study方法放在了哪裡?供誰使用?答:放在了Student類的原型物件上,供例項使用。
2、通過Student例項呼叫study時,this指向誰?答:指向Student的例項。
以上就是對類的一個複習,並沒有把類的所有知識都進行復習,只是重新梳理了比較重要也是比較難理解的部分。總結如下:
1、類中的構造器不是必須寫的,要對例項進行一些初始化的操作時,如新增一些指定屬性時才寫。如果在Student的構造器中加一行 this.job = '程式設計師'也是可以的,引數列表不需要變,它表示Student締造的例項物件的工作都是程式設計師
2、如果A類繼承B類,且A類中寫了構造器,那麼A類構造器中super是必須要呼叫的
3、類中定義的方法都是放在了類的原型物件上,供例項使用
由於之前一直沒能理解類、例項物件、原型鏈等具體概念以及相互之間的聯絡,故寫下這篇部落格來幫助理解,如有錯誤請指正。
部落格中原型物件及原型鏈部分參考了https://www.jianshu.com/p/ddaa5179cda6這篇文章。