JS中的OOP
OOP
為我們解決了什么問題?想象一下,我們希望為教師提供一個平臺,每位注冊的教師都可以提交分數,并為課程分配作業和其他內容。
如果有一個地方(在本例中是一個對象),可以訪問所有教師的數據(例如他們的姓名、職業和班級列表)以及前面提到的那些功能,那就太好了。簡而言之,就是將數據和方法封裝或者捆綁在一起。
為了實現我們的目的,我們創建一個函數來接收教師的數據并返回一個包含這些數據的對象以及每個教師能夠執行的方法。
const teacherCreator = (name: string, profession: string, classes: string[]) => {const newTeacher = {};newTeacher.name = name;newTeacher.profession = profession;newTeacher.classes = classes;newTeacher.submitMark = function(mark: number, studentId: number) {//....console.log(`學生${studentId} 的分數是${mark}。`)}newTeacher.assignHomework = function(homework: string, classId: number) {// ....}
}
這是我們的teacherCreator
功能演示。通過這種方式,我們實現了數據和方法的封裝。但有一個問題值得我們考慮。
內存耗用大
假設我們在有 1000 名教師,我們的這些消耗內存方法會在每個教師對象中重復定義。
但是我們只想要一份函數副本。將所有方法都初始化在一個地方,并且每當我們嘗試調用其中任何一個方法時,我們都從那里選擇它并避免這種重復,這不是更有效嗎?
我們可以在 JavaScript
中通過多種解決方案完成同樣的事情。
方法一:工廠函數
我們可以將實現teacherCreator
函數的方式更改為:
const teacherCreator = (name: string, profession: string, classes: string[]) => {const newTeacher = Object.create(teacherFunctionStore);newTeacher.name = name;newTeacher.profession = profession;newTeacher.classes = classes;// 返回一個對象return newTeacher;
}// 創建一個對象包含所有方法
const teacherFunctionsStore = {submitMark: function(mark: number, studentId: number) {//....console.log(`學生${studentId} 的分數是${mark}。`)},assignHomework: function(homework: string, classId: number) {// ....}
}const teacher1 = teacherCreator('Leo', 'English', ['A1-English']);
teacher1.assignHomeWork('do workbook', 10)
在上面的代碼塊中,我們不會在每個教師對象上創建這些方法。我們只是有一個地方——另一個對象——來存儲所有方法。現在的問題是 JavaScript
如何知道在哪里找到這些方法并執行它們。
JavaScript 如何執行這段代碼?
下面我們將逐步指導如何在所有方法中執行此代碼。一般來說,在 JavaScript
中執行時,每段代碼所發生的情況都是完全相同的。
JavaScript
會在全局內存中看到teacherCreator
,它將把它創建為一個函數。順便說一下,它不會進入函數內部。- 接下來,它將看到我們的
teacherFunctionStore
對象,并使用其中的方法在全局內存中啟動它。 - 代碼的下一行是一個名
teacher1
為的變量,它將在全局內存中初始化,但尚未設置值。因此,JavaScript
將進入teacherCreator
執行上下文中的函數內部。 - 在執行上下文內部 -每個函數調用都會創建一個新的執行上下文- 首先是將函數的參數設置在該執行上下文的本地內存中。
Object.create()
將為我們創建一個空對象。我們已經將teacherFunctionStore
傳遞給了它,因此它將使用該對象__proto__
的屬性來引用newTeacher
對象。該參考在圖中用紅線突出顯示。
const newTeacher = Object.create(teacherFunctionStore);
- 然后,我們將向對象添加屬性并將其返回到全局內存中——設置
teacher1
的值。
JavaScript 現在如何調用這些方法?
現在的問題是teacher1.assignHomework()
如何執行。這些方法不在對象本身內。
答案是非常清楚的。當我們調用對象的方法時,JavaScript
將首先查看該對象以查找該函數。如果找到它,它將執行它。在我們的例子中,它在我們的teacher1
對象上找不到assignHomework
。它應該拋出錯誤嗎?當然不是。至少現在還不行。
JavaScript
不會很快放棄。如果屬性或方法不在對象本身內部,它將在對象的__proto__
屬性中查找。
我們已經知道,我們的對象teacher1
會通過__proto__
鏈接到teacherFunctionStore
,JavaScript
會找到其中的方法并執行它。
方法2:構造函數
使用new
關鍵字來實現我們的功能,該關鍵字可以自動為我們完成這些鏈接工作。
function TeacherCreator(name: string, profession: string, classes: string[]) {this.name = name;this.profession = profession;this.classes = classes;
}TeacherCreator.prototype.submitMark = function(mark: number, studentId: number) {//....console.log(`學生${studentId} 的分數是${mark}。`)
},TeacherCreator.prototype.assignHomework = function(homework: string, classId: number) {// ....
}const teacher1 = new TeacherCreator('Leo', 'English', ['A1-English']);
teacher1.assignHomeWork('do workbook', 10)
TeacherCreator
構造函數前面的 new
關鍵字將為我們自動執行兩件事:
- 創建一個新的教師對象
- 返回新創建的教師對象
構造函數如何使用 new 關鍵字在幕后執行?
- 第一行是定義
TeacherCreator
構造函數。JavaScript
中的每個函數也是一個對象。因此,這里我們有一個函數-對象組合,如上圖所示。每當我們想要訪問函數部分時,我們都使用()
符號,而對于對象,我們使用.
符號。 - 在接下來的代碼行中,我們將在對象
TeacherCreator
部分的原型對象上設置一些方法,而不是其函數內。 - 我們在全局內存中定義一個
teacher1
常量。在執行上下文中執行函數之前我們不知道它的值。 - 在執行上下文中,首先要處理的還是函數參數。
new
關鍵字將為我們做的所有事情都以藍色列出。- 創建了包含給我們的函數的數據的對象。對象會在
new
關鍵字的幫助下自動this設置__proto__
為prototype
對象。 - 現在,
this
返回的teacher1
對象將是最終值,并且該執行上下文將被關閉。 - 這里調用我們創建的對象上的方法。
JavaScript
將首先查找teacher1
對象上的assignHomwork
方法。它進入teacher1
的__proto__
對象,但沒有找到它,但__proto__
鏈接到prototype
對象并在那里找到它并執行該函數。
ES6 class
class
關鍵字語法的作用與前面方法中TeacherCreator
構造函數的作用完全相同。然而,它給我們帶來了編寫更快代碼的好處,并且看起來與其他語言中實現的 OOP
類似。
class TeacherCreator {constructor(name, profession, classes){this.name = name;this.profession = profession;this.clasess = clasess;}// methods that will be accessible in prototype later on:submitMark = function(mark: number, studentId: number) {//....console.log(`學生${studentId} 的分數是${mark}。`)},assignHomework = function(homework: string, classId: number) {// ....}
}
ES6 類是否改變了迄今為止 OOP 的實現方式?
盡管我們的代碼現在看起來更加清晰易讀,但幕后的整個原理仍然是一樣的。JavaScript
仍然會為我們的TeacherCreator
類創建函數-對象組合。類內部的constructor
方法與我們在構造函數方法中的組合中的函數相同。
總之,重復相同的過程,但變得更加自動化和干凈。這是 JavaScript
中 OOP
背后的一般過程,基本上是原型繼承。