JS 中的 OOPS - 终极版
🔥连接:https://www.subham.online
🔥仓库:https://github.com/Subham-Maity/OOPS-in-JS-Ultimate
🔥GitHub:https://github.com/Subham-Maity
🔥推特:https://twitter.com/TheSubhamMaity
🔥领英:https://www.linkedin.com/in/subham-xam
🔥Insta:https://www.instagram.com/subham_xam
✅ 正在寻找 JAVA 中 OOP 概念的最佳笔记?这里有一个:
> “JAVA 中的 OOPS - Ultimate” - 请查看此
▶️ 简介
⭐什么是 OOP?
面向对象编程是一种通过创建对象来解决问题的方法。
⭐ OOP 的 4 个支柱
OOP中的术语
- 抽象——隐藏内部细节(仅显示必要信息!)
- 封装——将各种组件放在一起(在胶囊中)的行为
- 继承——从现有事物中衍生出新事物的行为
- 多态性——一个实体,多种形式
▶️ 原型和原型
JavaScript的对象有一个特殊的属性,prototype
称为null
当我们尝试从对象中读取属性时,如果缺少该属性,JavaScript 会自动从原型中获取它。这称为原型继承
⭐ 设置原型
我们可以通过设置 来设置原型__proto__
。如果我们从对象中读取一个属性,而该属性不在对象中,但在原型中,JavaScript 将从原型中获取该属性。如果对象中有一个方法,则会从对象中调用该方法。如果对象中没有该方法,但原型中有,则会从原型中调用该方法。
⭐示例:
//It will work properly
let p = {
run : () => {
console.log("run")
}
}
p.run()//Output: - run
//Let's define another property
let a = {
name : " subham"
}
a.run() //TypeError: a.run is not a function
//Now with proto
let b = {
name : " subham"
}
b.__proto__ = p
b.run() //Output: - run
简单来说,你可以继承某个对象的原型,让另一个对象继承它。这叫做原型继承。
//It will work properly
let p = {
run : () => {
console.log("p run")
}
}
p.run()//Output: - p run
//Now with proto
let b = {
run : () => {
console.log("b run")
}
}
b.__proto__ = p
b.run() //Output: - b run
如果某个属性或方法已存在于对象中,JavaScript 将使用该属性或方法。如果该属性或方法不存在于对象中,但存在于原型中,JavaScript 将从原型中获取它。在本例中,由于该run
方法已存在于b
对象中,因此它将打印“b run”。
▶️ 类和对象
- 在面向对象编程中,类是特定类型对象中的方法和变量的模板定义
- 在面向对象编程中,对象是已在内存中分配的类(或结构)的特定实例。
⭐示例:
//class
class GoogleForm {
submit() {
console.log(this.name + " " + this.roll + " Your form submitted")
}
cancel() {
console.log(this.name + " " + this.roll +" Your form cancelled")
}
fill(given_name , roll) {
this.name = given_name
this.roll = roll
}
}
//object
const student1Form = new GoogleForm()
student1Form.fill("Rahul" , 24)
const student2Form = new GoogleForm()
student2Form.fill("Raj" , 25)
student2Form.cancel()
student1Form.submit()
student2Form.submit()
▶️ 构造函数
在 JavaScript 中,构造函数是一个特殊的函数,它创建和初始化对象,设置它们的初始状态和属性。
假设他们忘记填写表格并单击提交按钮,它将抛出未定义!
class Form {
submit() {
console.log(this.name + ": Your form is submitted for train number: " + this.trainno)
}
cancel() {
console.log(this.name + ": This form is cancelled for train number: " + this.trainno)
this.trainno = 0
}
fill(givenname, trainno) {
this.name = givenname
this.trainno = trainno
}
}
let myForm1 = new Form()
let myForm2 = new Form()
//
// myForm1.fill("Gaurav", 1234)
//
// myForm2.fill("Rahul", 5678)
myForm1.submit()
myForm2.submit()
myForm2.cancel()
// Output: undefined: Your form is submitted for train number: undefined
// Output: undefined: Your form is submitted for train number: undefined
// Output: undefined: This form is cancelled for train number: undefined
现在创建构造函数,
class Form {
constructor() {
this.name = "Gaurav"
this.trainno = 0
}
submit() {
console.log(this.name + ": Your form is submitted for train number: " + this.trainno)
}
cancel() {
console.log(this.name + ": This form is cancelled for train number: " + this.trainno)
this.trainno = 0
}
fill(givenname, trainno) {
this.name = givenname
this.trainno = trainno
}
}
let myForm1 = new Form()
let myForm2 = new Form()
// myForm1.fill("Gaurav", 1234)
//
// myForm2.fill("Rahul", 5678)
myForm1.submit()
myForm2.submit()
myForm2.cancel()
// Output: Gaurav: Your form is submitted for train number: 0
// Output: Gaurav: Your form is submitted for train number: 0
// Output: Gaurav: This form is cancelled for train number: 0
⭐ 构造函数的类型
- 非参数化构造函数:没有参数的构造函数。
class Example {
constructor() {
this.property = "default value";
}
}
- 参数化构造函数:带有参数的构造函数。
class Example {
constructor(value) {
this.property = value;
}
}
- 复制构造函数:JavaScript 没有像 C++ 或 Java 那样内置的复制构造函数。但是,你可以创建一个方法来复制对象。
class Example {
constructor(value) {
this.property = value;
}
copy() {
return new Example(this.property);
}
}
const original = new Example("original value");
const copy = original.copy();
与 C++ 等语言不同,JavaScript 没有析构函数。相反,JavaScript 依赖于高效的垃圾收集器来自动释放内存。
▶️ 继承
一个类从另一个类派生属性和特征的能力称为继承。
⭐为什么?
如果你不知道什么是继承
class Animal {
constructor(name, color , age) {
this.name = name
this.color = color
this.age = age
}
run() {
console.log(this.name + ' is running')
}
shout() {
console.log(this.name + ' is shouting')
}
sleep() {
console.log(this.name + ' is sleeping')
}
}
//If you are nub developer you will do
class Monkey {
constructor(name, color) {
this.name = name
this.color = color
}
run() {
console.log(this.name + ' is running')
}
shout() {
console.log(this.name + ' is shouting')
}
sleep() {
console.log(this.name + ' is sleeping')
}
eatBanana() {
console.log(this.name + ' is eating banana')
}
}
const animal_1 = new Monkey('Simba monkey', 'Brown', 2)
const animal_2 = new Animal('Donkey', 'White', 3)
animal_1.eatBanana()
animal_2.shout()
如果你知道
//Parent Class - Base Class
class Animal {
constructor(name, color , age) {
this.name = name
this.color = color
this.age = age
}
run() {
console.log(this.name + ' is running')
}
shout() {
console.log(this.name + ' is shouting')
}
sleep() {
console.log(this.name + ' is sleeping')
}
}
//Child Class - Derived Class
class Monkey extends Animal{
eatBanana() {
console.log(this.name + ' is eating banana')
}
//you can also add new methods
hide() {
console.log(this.name + ' is hiding')
}
}
const animal_1 = new Monkey('Simba monkey', 'Brown', 2)
const animal_2 = new Animal('Donkey', 'White', 3)
animal_1.eatBanana()
animal_1.run()
animal_1.hide()
animal_2.shout()
⭐ 继承类型
- 单继承当一个类继承另一个类时,这称为单级继承。
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
const triangle = new Triangle();
triangle.area(10, 5); // Output: 25
- 分层继承被定义为从基类派生多个类的过程。
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
class Circle extends Shape {
area(r) {
console.log(3.14 * r * r);
}
}
const triangle = new Triangle();
triangle.area(10, 5); // Output: 25
const circle = new Circle();
circle.area(7); // Output: 153.86
- 多级继承是从另一个派生类派生一个类的过程。
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
class EquilateralTriangle extends Triangle {
constructor(side) {
super();
this.side = side;
}
area() {
console.log((Math.sqrt(3) / 4) * this.side * this.side);
}
}
const equilateralTriangle = new EquilateralTriangle(5);
equilateralTriangle.area(); // Output: 10.825317547305486
- 混合继承是简单继承、多重继承和层次继承的组合。JavaScript 并不直接支持多重继承,但我们可以使用 mixin 实现类似的行为。
class Shape {
area() {
console.log("Displays Area of Shape");
}
}
class Triangle extends Shape {
area(h, b) {
console.log((1/2) * b * h);
}
}
class Circle extends Shape {
area(r) {
console.log(3.14 * r * r);
}
}
const mixin = (Base) => class extends Base {
perimeter() {
console.log("Calculates Perimeter");
}
};
class EquilateralTriangle extends mixin(Triangle) {
constructor(side) {
super();
this.side = side;
}
area() {
console.log((Math.sqrt(3) / 4) * this.side * this.side);
}
}
const equilateralTriangle = new EquilateralTriangle(5);
equilateralTriangle.area(); // Output: 10.825317547305486
equilateralTriangle.perimeter(); // Output: Calculates Perimeter
▶️ 方法覆盖
如果超类和子类中定义了相同的方法,那么子类的方法将覆盖超类的方法
- 一般的
class human {
constructor(name , age , body_type) {
this.name = name
this.age = age
this.body_type = body_type
}
getName() {
console.log("The name of the human is : ", this.name)
}
getAge() {
console.log("The age of the human is :", this.age)
}
getBodyType() {
console.log("The body type of the human is :", this.body_type)
}
}
class student extends human {}
const student_1 = new student("Subham" , 24 , "Thin")
student_1.getAge() //The age of the human is : 24
⭐ 超级关键字 - 类型
super 关键字用于调用父类的构造函数来访问其属性和方法。
重写构造函数
class Human {
constructor(name, age, bodyType) {
this.name = name;
this.age = age;
this.bodyType = bodyType;
}
getName() {
console.log("The name of the human is:", this.name);
}
getAge() {
console.log("The age of the human is:", this.age);
}
getBodyType() {
console.log("The body type of the human is:", this.bodyType);
}
}
class Student extends Human {
constructor() {
super("Rahul", 80, "Fat");
}
}
const student1 = new Student();
student1.getName(); // The name of the human is: Rahul
重写方法
class Human {
constructor(name, age, bodyType) {
this.name = name;
this.age = age;
this.bodyType = bodyType;
}
getName() {
console.log("The name of the human is:", this.name);
}
getAge() {
console.log("The age of the human is:", this.age);
}
getBodyType() {
console.log("The body type of the human is:", this.bodyType);
}
}
class Student extends Human {
constructor() {
super("Rahul", 80, "Fat");
}
// Overriding using super keyword in child class
getAge() {
super.getAge();
console.log("The age of the student is:", 20);
}
}
const student1 = new Student();
student1.getAge(); // The age of the human is: 80
// The age of the student is: 20
⭐ 方法覆盖的要点
-
相同的方法名称:子类中的方法必须与父类中的方法具有相同的名称。
-
相同参数:子类中的方法必须具有与父类方法相同的参数列表。
-
IS-A 关系:方法覆盖仅发生在具有 IS-A 关系(继承)的两个类中。
-
访问修饰符:覆盖方法可以具有限制较少的访问修饰符,但不能具有限制较多的访问修饰符。
-
Super 关键字:您可以使用该
super
关键字从父类调用重写的方法。
⭐ 附加说明
注1
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
}
const student_1 = new student()
student_1.eat()
//Human class constructor
// Human can eat
如果你没有在子类中明确定义构造函数,JavaScript 将自动为你创建一个,并使用 super() 调用父类的构造函数。
像这样
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor(...arg) {
super(...arg);
}
}
const student_1 = new student()
student_1.eat()
注 2
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor() {
console.log("This is student class constructor")
}
}
const student_1 = new student()
student_1.eat()
// console.log("This is student class constructor")
//ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
您必须像这样使用 super 关键字
class human {
constructor() {
console.log("Human class constructor")
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor() {
super()
console.log("This is student class constructor")
}
}
const student_1 = new student()
student_1.eat()
注 3
class human {
constructor(name) {
console.log("Human class constructor" , name)
this.name = name
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor(name) {
this.name = name //not allow
super()
console.log("Student class constructor" , name)
}
}
const student_1 = new student("subham")
student_1.eat()
// this.name = name
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
在 super 关键字之后你可以使用这个
class human {
constructor(name) {
console.log("Human class constructor" , name)
this.name = name
}
eat() {
console.log("Human can eat")
}
}
class student extends human {
constructor(name) {
super()
this.name = name
console.log("Student class constructor" , name)
}
}
const student_1 = new student("subham")
student_1.eat()
// Human class constructor undefined
// Student class constructor subham
// Human can eat
▶️ 方法重载
类中有两个或多个具有相同名称和不同参数的方法(或函数)
⭐ 我们可以在 JavaScript 中重载函数吗?
JavaScript 本身并不支持像其他一些语言(例如 Java)中那样的方法重载。这意味着你不能在同一个类中定义多个同名但参数不同的方法。不过,你可以使用一些技巧来实现类似的功能,例如检查单个方法中参数的数量和类型。
你不能在 JS 中做到这一点
class Calculator {
add(a, b) {
return a + b;
}
add(a, b, c) {
return a + b + c;
}
}
const calc = new Calculator();
console.log(calc.add(1, 2)); // This will throw an error because the first add method is overwritten
如果你愿意,你可以通过这样做来实现
class Calculator {
add(...args) {
if (args.length === 2) {
return args[0] + args[1];
} else if (args.length === 3) {
return args[0] + args[1] + args[2];
} else {
throw new Error("Invalid number of arguments");
}
}
}
const calc = new Calculator();
console.log(calc.add(1, 2)); // Output: 3
console.log(calc.add(1, 2, 3)); // Output: 6
▶️访问修饰符
访问修饰符是用于设置类成员可访问性的关键字
⭐ 访问修饰符的类型
- 公共:声明为公共的成员可从任何其他类访问。
- 受保护的:声明为受保护的成员可在同一类中以及由派生类实例访问。
- 私有:声明为私有的成员只能在同一个类内访问。
⭐ 无障碍设施表
修饰符 | 父类 | Child 类 | 课外 |
---|---|---|---|
民众 | ✔️ | ✔️ | ✔️ |
受保护 | ✔️ | ✔️ | ❌ |
私人的 | ✔️ | ❌ | ❌ |
⭐示例
1. 公共成员
公共成员可以从任何地方访问。
class Parent {
publicProperty = "I'm public";
publicMethod() {
return "This is a public method";
}
}
class Child extends Parent {
useParentPublic() {
console.log(this.publicProperty);
console.log(this.publicMethod());
}
}
const parent = new Parent();
const child = new Child();
console.log(parent.publicProperty); // Output: I'm public
console.log(parent.publicMethod()); // Output: This is a public method
child.useParentPublic();
// Output:
// I'm public
// This is a public method
在此示例中,publicProperty
和publicMethod
可通过以下方式访问:
- 在父类中
- 在 Child 类中
- 任何课程之外
2. 受保护成员(模拟)
在 JavaScript 中,我们按照惯例使用下划线前缀来表示受保护的成员。从技术上讲,它们仍然是公共的,但开发者同意不在类或其子类的外部直接访问它们。
class Parent {
_protectedProperty = "I'm protected";
_protectedMethod() {
return "This is a protected method";
}
}
class Child extends Parent {
useParentProtected() {
console.log(this._protectedProperty);
console.log(this._protectedMethod());
}
}
const parent = new Parent();
const child = new Child();
child.useParentProtected();
// Output:
// I'm protected
// This is a protected method
// These work, but violate the convention:
console.log(parent._protectedProperty);
console.log(parent._protectedMethod());
在这种情况下:
_protectedProperty
并且_protectedMethod
可以在 Parent 中访问- 它们也可以在 Child(继承)中访问
- 从技术上讲,它们可以在外部访问,但这违反了惯例
3. 私有成员
私有成员是真正私有的,并且只能在定义它们的类中访问。
class Parent {
#privateProperty = "I'm private";
#privateMethod() {
return "This is a private method";
}
usePrivate() {
console.log(this.#privateProperty);
console.log(this.#privateMethod());
}
}
class Child extends Parent {
tryToUseParentPrivate() {
// These would cause errors if uncommented:
// console.log(this.#privateProperty);
// console.log(this.#privateMethod());
}
}
const parent = new Parent();
const child = new Child();
parent.usePrivate();
// Output:
// I'm private
// This is a private method
// These would cause errors:
// console.log(parent.#privateProperty);
// console.log(parent.#privateMethod());
// child.tryToUseParentPrivate();
在这种情况下:
#privateProperty
并且#privateMethod
只能在 Parent 中访问- 尽管 Child 扩展了 Parent,但它们在 Child 中无法访问
- 它们在课堂之外根本无法访问
关键要点
- 公共成员(默认)可在任何地方访问。
- 受保护的成员(约定
_
)可以在类和子类中访问,但不应在外部访问(尽管从技术上讲可以)。 - 私有成员(带有
#
)只能在定义类内访问,而不能在子类或外部访问。 - 当使用受保护的成员时,它们在可访问性方面的行为与公共成员一样,但开发人员同意将它们视为受保护的成员。
- 真正的隐私和封装只有使用语法的私有成员才能实现
#
。
▶️ 静态
static 关键字为类定义静态方法或字段
静态方法是属于类本身的方法,而不是属于该类的任何特定实例的方法。
class Animal {
constructor(name) {
this.name = Animal.capitalize(name);
}
static capitalize(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
}
walk() {
console.log(`Animal ${this.name} is walking`);
}
}
const animal = new Animal("lion");
animal.walk(); // Output: Animal Lion is walking
console.log(Animal.capitalize("elephant")); // Output: Elephant
要点:
- 使用关键字将该方法
capitalize
声明为静态的static
。 - 它在类(
Animal.capitalize
)上被调用,而不是在实例上。 - 它可以在构造函数内部或使用类名的其他方法中使用。
⭐ 继承和静态方法
静态方法被子类继承:
class Human extends Animal {
static greet() {
console.log("Hello!");
}
}
const human = new Human("john");
human.walk(); // Output: Animal John is walking
console.log(Human.capitalize("sarah")); // Output: Sarah
Human.greet(); // Output: Hello!
笔记:
- 该类从
Human
继承了静态方法。capitalize
Animal
Human
也可以定义自己的静态方法,例如greet
。
⭐ 从非静态方法调用静态方法
您可以从非静态方法调用静态方法,但需要使用类名:
class Calculator {
static add(a, b) {
return a + b;
}
multiply(a, b) {
// Using a static method in a non-static method
return Calculator.add(a, 0) * b;
}
}
const calc = new Calculator();
console.log(calc.multiply(3, 4)); // Output: 12
console.log(Calculator.add(5, 6)); // Output: 11
⭐ 静态方法 vs. 实例方法
下面通过比较来说明差异:
class MyClass {
static staticMethod() {
return "I'm a static method";
}
instanceMethod() {
return "I'm an instance method";
}
}
console.log(MyClass.staticMethod()); // Output: I'm a static method
const obj = new MyClass();
console.log(obj.instanceMethod()); // Output: I'm an instance method
// This would throw an error:
// console.log(MyClass.instanceMethod());
// This would also throw an error:
// console.log(obj.staticMethod());
⭐ 静态方法的用例
- 实用函数:不需要对象状态的方法。
- 工厂方法:创建具有特殊属性的实例。
- 缓存或固定配置:存储所有实例的共享数据。
工厂方法的示例:
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
static createAdmin(name) {
return new User(name, "admin");
}
}
const admin = User.createAdmin("Alice");
console.log(admin.role); // Output: admin
⭐ 关键要点
- 静态方法是在类上定义的,而不是实例上定义的。
- 使用类名来调用它们:
ClassName.methodName()
。 - 它们可以被子类继承。
- 它们不能直接访问实例属性或方法。
- 它们对于实用功能、工厂方法和管理类级数据很有用。
- 您不能在实例上调用静态方法,也不能在类上调用实例方法。
▶️ Getter 和 Setter
Getters 和 setters 是分别允许你获取和设置对象值的函数
//getter setter
class human {
constructor(name, age) {
this._name = name;
}
get getName() {
return this._name;
}
set setName(name) {
this._name = name;
}
}
const person = new human("", 0);
person.setName = "Raj";
person.setAge = 25;
console.log(person.getName);
console.log(person.getAge);
//Raj
//25
▶️instanceOf 运算符
检查对象是否是类、子类或接口的实例
//getter setter
class human {
constructor(name, age) {
this.name = name;
this.age = age;
}
get getName() {
return this.name;
}
set setName(name) {
this.name = name;
}
get getAge() {
return this.age;
}
set setAge(age) {
this.age = age;
}
}
const person = new human("", 0);
person.setName = "Raj";
person.setAge = 25;
console.log(person.getName);
console.log(person.getAge);
const person1 = "Subham"
console.log( person instanceof human)//true
console.log( person1 instanceof human)//false
对于子类,它也返回 true
//getter setter
class human {
constructor(name, age) {
this.name = name;
this.age = age;
}
get getName() {
return this.name;
}
set setName(name) {
this.name = name;
}
get getAge() {
return this.age;
}
set setAge(age) {
this.age = age;
}
}
class Coder extends human {
constructor(name, age, language) {
super(name, age);
this.language = language;
}
}
const person = new human("", 0);
const subham = new Coder("subham", 22, "java");
person.setName = "Raj";
person.setAge = 25;
console.log( person instanceof human)
console.log( subham instanceof human)
▶️ 封装
封装是一种限制直接访问对象某些组件的方法
class BankAccount {
#balance; // Private field
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// console.log(account.#balance); // Syntax error: private field
//Encapsulation
const user = {
firstName: "John",
lastName: "Doe",
age: 25,
getAgeYear: function() {
return new Date().getFullYear() - this.age;
}
}
console.log(user.getAgeYear());
▶️ 多态性
多态性的意思是“多种形式”,当我们有许多通过继承相互关联的类时就会出现多态性。
// Parent class
class Animal {
makeSound() {
console.log("The animal makes a sound");
}
}
// Child classes
class Dog extends Animal {
makeSound() {
console.log("The dog barks");
}
}
class Cat extends Animal {
makeSound() {
console.log("The cat meows");
}
}
// Function to demonstrate polymorphism
function animalSound(animal) {
animal.makeSound();
}
// Usage
const animal = new Animal();
const dog = new Dog();
const cat = new Cat();
animalSound(animal); // Output: The animal makes a sound
animalSound(dog); // Output: The dog barks
animalSound(cat); // Output: The cat meows
▶️抽象
抽象是隐藏复杂的实现细节并仅显示对象必要特征的概念。
// Abstraction: Hiding complex implementation details and showing only the necessary features of an object.
// Abstract class
class Vehicle {
constructor(brand) {
this.brand = brand;
}
// Abstract method
start() {
throw new Error("Method 'start()' must be implemented.");
}
getBrand() {
return this.brand;
}
}
// Concrete class
class Car extends Vehicle {
start() {
return `${this.brand} car is starting...`;
}
}
// Usage
const myCar = new Car("Toyota");
console.log(myCar.getBrand()); // Output: Toyota
console.log(myCar.start()); // Output: Toyota car is starting...