我不懂面向对象编程!
如果你是一位正被 JavaScript 弄得焦头烂额或深陷教程泥潭的初学者,我敢肯定你一定读过这篇文章的标题,并给了我一个想象中的拥抱,因为你也能感同身受。说实话,我并没有现成的蓝图来帮助你理解面向对象编程,但正如我曾经在某处读到的那样,最好的学习方式就是教……所以我在这里,在 Fun 和 Janae Monelle 的《We Are Young》的背景音乐下,分享我个人关于面向对象编程的点滴。希望这篇文章能对某些新手有所帮助。
附言:我欢迎任何能帮助其他新手的贡献、资源和评论。如果我们互相帮助,我们就能比太阳更闪耀。
因此,我们首先需要知道的是传统的定义……
面向对象编程将一组数据属性与函数或方法组合成一个称为“对象”的单元。多个独立对象也可以从同一个类中实例化(或表示),并以复杂的方式相互交互。
通常,OOP 是基于类的,这意味着类定义数据属性和功能作为创建对象(类的实例)的蓝图。
我恰好非常喜欢汽车,所以我的第一个简单示例是考虑一个代表汽车的类。“汽车”类将包含一些属性来表示汽车的名称、型号、车轮数量、颜色等信息。也许这样会更熟悉一些;
let car = {
name: "Mercedes Benz",
model: "CLA 4DR Coupe",
numOfWheels: 4,
chassisNum: 0123456789,
color: "white"
};
我想继续讨论一下大家所指的面向对象编程的 4 个基本概念,即封装、抽象、继承和多态性......但在我开始使用这些词之前,如果我们真正理解如何使用 OOP,然后亲眼看看这 4 个基本概念的实际作用,不是更好吗?
我们已经成功创建了第一个具有不同属性和值的类。我们可以使用点符号访问“car”对象中的属性,然后访问其值。请看下面的代码:
console.log(car.model); //"CLA 4DR Coupe"
在上面的代码中,我们在名为“car”的对象上使用点符号,然后使用属性“model”来访问“CLA 4DR Coupe”的值
很酷吧?类中可能包含一些私有数据,例如“chassisNum”,这些数据不应该暴露给程序中的其他对象。通过将这些数据成员封装为类中的私有变量,外部代码就无法直接访问它,并且它在类中仍然是安全的。
在 OOP 中,我们通过将数据和对该数据进行操作的函数绑定到一个单元(即类)中进行封装。
通过这样做,我们可以向外界隐藏类的私有细节,只暴露与类交互所需的重要功能。如果一个类不允许调用代码直接访问其私有数据,我们就说它封装得很好。好了,你理解了封装的含义。
如果不知道方法是什么,学习面向对象编程就毫无意义。方法是对象拥有的一种特殊属性。它们只是函数属性。它们为对象添加了不同的行为。我认为它们能让对象在处理事情时更加灵活。例如,
let car = {
name: "Range Rover Evogue",
price: 70000,
describeCar: function() {
return "That car speeding on the highway is a " + car.name + " and it costs " + car.price + " USD.";}
};
car.describeCar(); //"That car speeding on the highway is a Range Rover Evogue and it costs 70000 USD."
上面的代码块有一个方法describeCar
,它是一个函数,它返回一个语句,告诉我们汽车的名称和价格。(顺便说一句,我不知道路虎揽胜的价格)。
注意,该方法在返回语句中使用and访问了name
and属性。现在想想,用方法可以做多少很棒的事情……是不是很棒?price
car.name
car.price
不过,还有另一种方法可以访问name
和price
属性……是的,您可能听说过…… “this”关键字(此时,您可能想知道……谁在命名这些编码概念,因为字面上的“this”是什么意思,对吧?哈哈)
在我看来, “这”的存在是为了使代码可重用并且更易于阅读,我想其他人也认同这一点。
在上一个示例中,我们有一个方法describeCar
,它使用car.name
和点符号来访问return 语句中和属性car.price
的值。 回想一下,name
price
describeCar: function() {
return "That car speeding on the highway is a " + car.name + " and it costs " + car.price + " USD.";}
尽管这是访问对象“car”属性的一种非常正确的方法,但是您是否曾经问过自己,当您在代码库的第 235、410、720、850、1100、1425、1658、1780 和 3800 行访问此对象及其属性时,由于某种原因,变量名称从 更改为"car"
在"automobile"
为像 Mercedes 这样的大公司工作时,会发生什么?
你的工作压力会更大,因为你必须更新所有引用已更改的原始名称的代码行,我们都知道这有多紧张。这时this
关键字就派上用场了。你可以将上一个示例中的初始代码重写如下:
let car = {
name: "Range Rover Evogue",
price: 70000,
describeCar: function() {
return "That car speeding on the highway is a " + this.name + " and it costs " + this.price + " USD.";}
};
car.describeCar();
现在,我们仅仅触及了皮毛,this
这是一个非常深奥且有时复杂的主题,上述内容绝对不是它的唯一用法。在这里,我们只是用this
in 来引用describeCar
与该方法关联的对象,也就是car
。因此,如果将对象变量car
更改为automobile
,甚至locomotive
,则无需car
在代码中查找所有对 的引用。就是这样……更简单,并且可全面复用。
现在我们已经解决了这个问题,让我们暂时扮演一下土木工程师的角色,讨论一下构造函数(顺便说一下,这是我在开一个并不好笑的玩笑)...
现在,想象一下,作为初学者,您第一次看到下面的功能......这可能就是现在正在发生的事情;
function Truck() {
this.name = "Ford Ranger 2018";
this.color = "Black";
this.price = 100000;
this.numWheels = 4;
this.yearOfProduction = 2018;
}
看起来很奇怪吧?因为我第一次看到它的时候也觉得很奇怪。函数应该返回一个语句、一个值或者其他你读到过的东西,对吧?它看起来像一个对象,甚至是一个方法,但方法总是在对象内部,这可不是“正常”对象的写法……别担心,这是一个构造函数。
构造函数是创建新对象的函数。它们定义新对象所拥有的属性和行为。这意味着,像上面的例子一样,以这种方式编写的函数将创建一个名为“Truck”的新对象,并将name
、color
和numOfWheels
属性yearOfProduction
及其对应的值附加到该对象上。this
指的是已创建的新对象。
请注意,Truck
对象是用大写字母定义的。构造函数这样定义是为了将其与其他非构造函数区分开来,这些函数不会像其他函数那样返回值。
和往常一样,新问题总是源于现有问题……如果我们想创建一个新对象,使其具有与上"Truck"
一个示例中的初始构造函数相同的属性,该怎么办?我们只需在之前的代码块下方添加以下代码行即可:
let fordTruck = new Truck();
该new
操作符将指示 JavaScript 创建一个Truck
名为 的对象的新副本fordTruck
。
请注意,如果您现在包含 **new
,您将不会得到结果,因为即使您排除故障并从这里到 Bethlehem 的 console.log 也不会创建新的对象**
因此,最终,如果您fordTruck.name
在控制台中输入,结果将给出我们初始卡车的值,this.name
因为fordTruck
现在具有的所有属性Truck
。
现在你知道构造函数的作用了,但如果你是个观察力敏锐的天才,你就会注意到,当我们创建新的构造函数时fordTruck
,它name
除了带有其他属性(例如color
、numOfWheels
和 yearOfProduction)之外,还带上了 属性。如果你想为每个 new 赋予不同的值,我们可以不断修改它们的名称,Truck
但假设你负责追踪福特工厂生产的数十万辆卡车呢?
你可以通过设计初始构造函数来更改或轻松创建新的卡车实例,Truck
使其接受任何可能需要更改的参数,例如卡车的名称、价格和颜色,并根据需要保留其他值。因此,我们重写了原始构造函数,使其接受如下所示的参数:
function Truck(name, price, color) {
this.name = name;
this.color = color;
this.price = price;
this.numWheels = 4;
this.yearOfProduction = 2018;
}
然后我们可以说;
let fourWheel = new Truck("Ranger", 175000, "gray");
当您执行此操作时,您将创建一个新实例,Truck
该实例将被命名fourWheel
,并将属性设置为新fourWheel
对象的新属性。
通过上述内容,构造函数现在非常灵活,因为它可以接受参数,并且我们可以在创建每辆卡车时为其定义新属性。
始终记住,构造函数根据共享的特征和行为将对象分组,并定义一个自动创建对象的蓝图
如果您想检查创建的新对象是否是构造函数的实例,请使用instanceof
运算符。
例如,在上面的最后一个例子中,
fourWheel instanceof Truck;
它将返回,true
因为该fourWheel
对象是使用Truck
构造函数创建的。
但如果我们说,
let saloonCar = {
name: "Ford Focus",
color: "white",
}
然后我们检查相同的saloonCar instanceof Truck;
,它将返回,false
因为saloonCar
不是使用Truck
构造函数创建的。
此外,Truck
构造函数还定义了五个属性(name、color、price、numOfWheels、yearOfProduction),这些属性直接在构造函数内部定义。这些属性被称为“自身属性”。
假设我们正在设置 3 个新的Truck
called实例firstCar
,secondCar
并且thirdCar
分别会有类似这样的内容;
let firstCar = new Truck("edge", "red", 30000);
let secondCar = new Truck("broncos", "black", 120000);
let thirdCar = new Truck("focus", "blue", 65000);
其他两个属性将保持不变numOfWheels
,yearOfProduction
因为没有为它们传递新的参数。
所有 5 个属性都称为自身属性,因为它们直接在实例对象上定义Truck
。这意味着firstCar
、secondCar
和thirdCar
都有各自独立的属性副本,并且 的每个其他实例Truck
也都有各自独立的属性副本。
您可能会问,所有这些的本质是什么,我们可以用自己的财产做什么......好吧,我们可以在编写代码时将它们推送到一个空数组中,如下所示;
let ownProps = [];
for(let property in secondCar) {
if(secondCar.hasOwnProperty(property)) {
ownProps.push(property);
}
}
这样,当我们时console.log(ownProps)
,它会将不同的属性打印secondCar
到空ownProps
数组中。
如果你仔细观察我们的代码,你肯定会发现,numOfWheels
对于所有的 实例, 都有相同的值Truck
。换句话说,它是一种重复变量。
如果您只有几个实例或 5 个原始汽车对象实例,那么这不是什么大问题...但是...您可能会在福特总部工作并使用您的代码来跟踪数百万辆四轮车,这意味着数百万个实例。
在上述情况下,原型prototype
就派上用场了。你可能会问,原型是做什么的?很简单……原型在原始对象的所有实例之间共享一个特定的属性。
Truck.prototype.numOfWheels = 4;
现在的所有实例都Truck
将具有该numOfWheels
属性。
forprototype
和firstCar
是构造函数secondCar
的一部分。Truck
Truck.prototype
总而言之,当谈到属性时,自身属性总是直接在对象本身上定义,而原型属性将在原型上定义。
那么,如果我们需要在原型中添加多个属性怎么办?您已经知道,如果我们必须一个接一个地添加,那将非常繁琐。更有效的方法是将原型设置为一个已经包含属性的新对象。如下所示:
Truck.prototype = {
numOfWheels: 4,
sound: function() {
console.log("Vroom! Vroom!!")
}
}
然后我们想quality
向原型添加一个方法。所有属性可以通过这种方式一次性添加:
Truck.prototype = {
numOfWheels: 4,
sound: function() {
console.log("Vroom! Vroom!!")
},
sound: quality() {
console.log("It is a super fast " + this.name);
}
};
永远不要忘记,每当手动将原型设置为新对象时,都要定义构造函数属性。为什么?原因很简单,因为当你手动设置原型时,它会清除构造函数属性,而如果你检查哪个构造函数创建了实例,结果将为 false。
总之,为了更好地理解原型链,您需要始终注意以下几点;
-
JavaScript 中的所有对象都有一个原型(除了少数例外)。
-
对象的原型就是一个对象。如果你对此感到困惑,我肯定也感到困惑。你应该去看看Javascript.info
-
原型也可以有自己的原型,因为原型是一个对象。例如;
function Car(name) {
this.name = name;
}
typeof Car.prototype; //the result for this will be "object"
let bugatti = new Car("Veyron");
bugatti.hasOwnProperty("name");
从上面可以看出,Car
= supertype for bugatti
bugatti
= subtype for Car
Car
= supertype for bugatti
Object 是两者的超类型Car
,并且bugatti
Object 是 JavaScript 中所有对象的超类型,因此,任何对象都可以使用该hasOwnProperty
方法。
在我暂停讨论这个问题之前,还有另一个重要的原则需要遵守,那就是继承原则。
重复的代码通常会造成问题,因为任何一处代码的修改都需要在多个地方进行修改,这只会给开发人员带来更多工作量,也更容易出错。现在假设我们有两个构造函数,我将以非洲两位最伟大的艺术家的名字命名(因为我可以,而且我们不必总是那么无聊);
Wizkid.prototype = {
constructor: Wizkid,
describe: function() {
console.log("My name is " + this.name + " and I always come late to my concerts in Nigeria");
}
};
Davido.prototype = {
constructor: Davido,
describe: function() {
console.log("My name is " + this.name + " and I always come late to my concerts in Nigeria");
}
};
我们可以看到,该describe
方法在两个地方重复,我们可以使用所谓的DRY 原则(不要重复自己)通过创建一个名为的超类型来优化此代码**Artistes**
;
function Artiste() {};
Artiste.prototype = {
constructor: Artiste,
describe: function() {
console.log("My name is " + this.name + " and I always come late to my concerts in Nigeria");
}
};
Artiste
由于您具有包含该方法的上述超类型describe
,因此您可以describe
从Wizkid
和中删除该方法Davido
。
Wizkid.prototype = {
constructor: Wizkid
};
Davido.prototype = {
constructor: Davido
};
好了,您刚刚成功创建了一个名为的超类型,Artiste
它定义了所有音乐家/艺术家共享的行为。
我现在就到此为止...您可以在Javascript.info上了解有关面向对象编程的基础知识以及高级概念的更多信息
你也可以通过评论分享,帮助其他新手学习更多知识,因为我的入门还只是皮毛。祝你和你的家人新年快乐,一路平安。
鏂囩珷鏉ユ簮锛�https://dev.to/resourcefulmind/i-do-not-know-object-oriented-programming-1bim