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 }
} // 創建一個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() 方法,並且要this之前!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 ,普通人繼承人類,人類繼承對象類
當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找(在構造函數中使用 this.demo = function demo() {},那么demo函數就在對象自身中,也叫作實例方法),如果有則直接使用,如果沒有則會去原型對象中尋找,如果找到則直接使用。如果沒有則去原型的原型中尋找,直到找到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這篇文章。
關於實例屬性(方法)和原型屬性(方法)的區別請參考 https://blog.csdn.net/lixiaonaaa/article/details/113801431