面向对象编程💊
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
介绍
作为一名开发人员,你会经常听到面向对象编程这个术语,并且随着编写代码量的增加,你会逐渐理解并运用这个概念。直到最近,我才决定深入研究它究竟是什么,以及利用它的优势所带来的更大益处。
以下是一个简单的示例,Object展示了 JavaScript 中 `<input type="text">` 元素的常见写法:
let myObject = {
myStringProperty: 'OOP',
myNumberProperty: 21,
myMethod: () => {
return this.myNumberProperty++;
}
}
定义
面向对象编程(OOP)是一种approach in programming in which data is encapsulated within objects and the object itself is operated on, rather than its component parts……
值得一提的是,JavaScript 是一种高度面向对象的语言。它遵循基于原型的模型(而非基于类的模型)。每种数据类型都有自己的原型,包含自身的属性和方法。调用这些原型的方法是使用关键字 `__init__` prototype,例如:`__init__` Array.prototype。通过这种方式,我们创建了一个数组实例。
用最简单的语言来解释面向对象编程(OOP)的最佳方式是:将对象想象成一个包含特征、特性、属性、功能等的盒子,用来定义一个概念或想法。例如,如果我们想用面向对象编程来描述一辆汽车,我们可以这样做:
let car = {
model: '...',
year: 0000,
engineOn: false,
start: _ => {
return this.engineOn = true;
},
stop: _ => {
return this.engineOn = false;
}
}
在上面的例子中,我们只是将一辆汽车建模成一个对象,其中包含了它的所有关键属性和方法,这些属性和方法都是汽车实际可以执行的功能。
好处
在面向对象编程(OOP)出现之前,JavaScript 编程主要采用过程式编程,这意味着使用大量的变量和函数来修改数据,最终实现预期的输出结果。在大多数情况下,这种方式易于理解,也比较直接。但问题在于,随着程序规模的扩大,其复杂性也会随之增加,最终可能导致大量函数散落在各处,重复的代码块容易出错,从而引发 bug。换句话说spaghetti code,面向对象编程提供了四个核心概念,可以帮助我们找到解决这个问题的方法。
封装
第一个好处是能够将信息封装成单元,从而便于访问和操作。这些单元就是对象,数据/变量就是我们的属性,而任何影响数据的操作就是我们的方法。
下面这个例子清楚地说明了封装的好处:
let baseSalary = 30000;
let overtime = 20;
let rate = 15;
function getWage(baseSalary, overtime, rate) {
return baseSalary + (overtime * rate);
}
在这个例子中,一边是包含 3 个变量的过程代码,另一边是一个执行算术运算并返回结果的函数,可以说它们是解耦的。
如果用面向对象编程(OOP)的视角来看,这段代码块会是这样的:
let employee = {
baseSalary = 30000,
overtime = 20,
rate = 15,
getWage: _ => {
this.baseSalary + (this.overtime * this.rate);
}
}
employee.getWage();
我们创建了一个新的员工对象,它包含三个属性和一个计算数据并返回结果的方法。其优势显而易见:如果您查看该getWage函数,会发现它没有传递任何参数;由于employee对象已在其属性中包含所有数据,我们可以利用作用域来使用它们this。employee对象内的所有属性和方法都高度相关,并被建模为一个整体。因此,我们可以说,过程式代码的一个典型特征是函数包含多个参数,但随着您编写更多面向对象编程 (OOP) 代码,您会遇到参数更少的函数。参数越少,就越容易使用和维护。
抽象
抽象的主要目的是将复杂的概念从人们的视野和思维中移除。你可以把CPU想象成一个带按钮的黑盒子,因为机箱里装着主板、线缆、电源、显卡、风扇等等,人们通常不会去仔细思考一台电脑运转所需的各种精密部件。由此可见,CPU的概念已经从用户的角度被抽象化了。
在面向对象编程(OOP)中,你也可以使用类似的技术,将一些属性和方法对外部隐藏起来。这样做的一些好处包括:
- 更简单的对象交互界面。
- 你降低了变革的影响。
通过使用容器单元,您可以只公开一些属性和方法,使代码更易于用户理解。此外,如果您更改对象中包含的这些私有方法,更改不会泄露到代码的外部全局作用域,从而最大限度地减少对程序整体功能的影响。这样就可以避免前面提到的代码混乱问题。
遗产
通过使用继承,我们可以避免编写冗余代码。对象可以被建模成类似“饼干模具”的结构,从而创建多个变量,每个变量都具有相同的属性和方法。这样,我们就创建了多个名称不同但功能相同的克隆对象。之后,你可以以特定的方式引用这些克隆对象,从而只影响其中一个。这就引出了object template构造函数的概念。通过使用构造函数,你可以生成在整个程序中具有相同可用性的对象实例,而无需为每个创建的元素重新定义所有属性和方法。
构造函数是为对象赋予特征的函数。以下是一个简单的示例:
function Person(name, age) {
this.name = name;
this.age = age;
this.greeting = _ => {
alert('Hi! I\'m ' + this.name + ', and I\'m ' + age + ' years old.');
};
}
在这个例子中,我们创建了一个人物角色,它具有姓名和年龄属性以及一个问候方法。要使用它,我们编写以下代码:
let person1 = new Person('Jimmy', 20);
let person2 = new Person('John', 21);
console.log(person1.name); // Jimmy
console.log(person2.name); // John
多态性
为了更好地理解多态性,我们将探究这个词的含义:它由“组合poly”和morph“形式”三个部分组成。在面向对象编程(OOP)中,多态性是一个核心概念,它允许以不同的形式执行同一个操作。它允许我们在不同的 JavaScript 对象上调用同一个方法。由于 JavaScript 不是类型安全的语言(除非使用 TypeScript 或严格模式),我们可以将任何类型的数据成员传递给方法。
回到我们的 Person 示例,我们可以看到该greeting方法如何返回两个不同的输出,因为它取决于name和 的值age。
person1.greeting(); // Hi! I'm Jimmy, and I'm 20 years old.
person2.greeting(); // Hi! I'm John, and I'm 21 years old.
多态性的主要优势在于,作为开发者,我们可以设计共享行为的对象,并利用继承来覆盖共享的行为。例如,如果我们想改变某个 Person 类实例的行为,创建一个运动员,我们可以这样做:
function Athlete(age, weight, speed){
this.age = age;
this.weight = weight;
this.speed = speed;
}
Employee.prototype = new Person();
之后,我们希望改变该greeting方法的行为,让用户知道我们新运动员的速度有多快:
Athlete.prototype.greeting = _ => {
alert('Hi! I\'m ' + this.name + ', I\'m ' + age + ' years old, and I can run up to ' + speed + ' miles per hour');
}
能够做到这一切,将提供一种用户友好且更优雅的方式来描述特定场景下根据输入类型需要多种结果的不同情况。这种特性在 switch/case 语句中会更易于扩展。
概括
面向对象编程允许我们将数据建模/设计为单元,从而带来诸多优势Encapsulation。它允许我们将相关的变量和函数分组在一起,降低复杂性,并在程序的不同部分重用代码。通过这种方式Abstraction,我们可以隐藏细节和复杂性,只显示必要的部分,从而帮助我们隔离更改的影响。此外,Inheritance它还有助于我们消除冗余代码,最后,通过这种方式,Polymorphism我们可以避免代码因包含多种情况而过载。