物件導向程式設計是一種透過建立物件來解決問題的方法。
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 將從原型中獲取它。在此範例中,由於b
物件中已存在run
方法,因此它將列印“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;
}
}
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
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
關鍵字從父類別呼叫重寫的方法。
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()
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()
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 中,本機不支援某些其他語言(如 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
存取修飾符是一個關鍵字,用於設定類別成員的可存取性
Public :聲明為 public 的成員可以從任何其他類別存取。
Protected :宣告為 protected 的成員可以在同一類別中和衍生類別實例中存取。
Private :宣告為私有的成員只能在同一個類別中存取。
|修改器|家長課 |兒童班|課外|
|----------------|------------|-------------|--- - ------------|
|公共| ✔️ | ✔️ | ✔️ |
|受保護| ✔️ | ✔️ | ❌ |
|私人| ✔️ | ❌ | ❌ |
公共成員可以從任何地方存取。
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類別中
任何班級之外
在 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 中存取(繼承)
從技術上講,它們可以在外部存取,但這違反了約定
私有成員是真正私有的,只能在定義它們的類別中存取。
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 內存取
即使它擴展了父級,它們也無法在子級中存取
在課堂之外根本無法存取它們
公共成員(預設)可以在任何地方存取。
受保護的成員(使用_
的約定)可以在類別和子類別內存取,但不應在外部存取(儘管從技術上講是可以的)。
私有成員(帶有#
)只能在定義類別中存取,不能在子類別中或外部存取。
使用受保護成員時,它們在可存取性方面的行為與公共成員類似,但開發人員同意將它們視為受保護的成員。
真正的隱私和封裝只能透過使用#
語法的私有成員來實現。
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
要點:
使用static
關鍵字將capitalize
方法宣告為靜態方法。
它是在類別( 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
類別繼承了Animal
的靜態capitalize
方法。
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
下面透過比較來說明差異:
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 是分別允許您取得和設定物件值的函數
//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
檢查物件是否為類別、子類別或介面的實例
//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...