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
背后的一般过程,基本上是原型继承。