Advanced OOP in JavaScript بالعربي
الدليل الشامل لفهم وتطبيق مبادئ الOOP في الجافاسكريبت
بعد الإنتهاء من الكورس سوف تكون قادر على:
ء✅ إنشاء واستخدام ال Objects في جافاسكريبت باحترافية
ء✅ التعامل مع ال Classes وال Constructors باحترافية
ء✅ فهم كامل لل Inheritance واستخدام الكلمة الأساسية super
ء✅ التعامل مع ال Static Properties وال Static Methods
ء✅ استخدام ال Getters وال Setters
ء✅ التعامل مع Public و Private Class Fields
ء✅ استخدام ال Private Methods
ء✅ فهم Static Initialization Blocks
الدرس الأول:
تعالوا نشوف إزاي بنعمل object في الجافاسكريبت:
1- Object Literal:
const car = {
color: "red",
speed: 200,
};
2- Object Constructor:
const car = new Object();
car.color = "red";
car.speed = 200;
3- Constructor Function:
function Car(color, speed) {
this.color = color;
this.speed = speed;
}
const car = new Car("red", 200);
4-Object.create()
:
const carPrototype = {};
const car = Object.create(carPrototype);
car.color = "red";
car.speed = 200;
5- ES6 class:
const Car {
constructor(color, speed){
this.color = color;
this.speed = speed;
}
}
const car = new Car("red", 200);
6-Function
Constructor:
const Car = new Function('color', 'speed', `
this.color = color;
this.speed = speed;
`);
const person = new Car("red", 200);
ازاي تأكسس ال Property بتاعة Object؟
1- Dot Notation
car.color = "red";
car.speed = 200;
2- Bracket Notation
car["color"] = "red";
car["speed"] = 200;
يفضل استخدام ال Dot Notation ولكن بنستخدم ال Bracket Notation لما تكون ال Key او ال Property Dynamic مثال:
let key = "color";
car[key] = "red";
ملاحظات مهمه:
١. لو ملاقيناش key أو property هيطلع undefined
console.log(car.key); // undefined
٢. لازم نعرف برده إن ال keys بيتحولوا ل String (Object Keys get Stringified)
car[1] = "one";
car["1"] = "one";
car[true] = "correct";
car["true"] = "correct";
٣. نعرف برده إن ال property ممكن تكون function
car.drive = function() {
console.log("driving");
};
ملاحظات متقدمة:
1- Property Shorthand in Object Literals ES6
let name = "John";
let age = 30;
let person = { name, age };
2- Computed Property Names ES6
let propName = "age";
let person = {
name: "John",
[propName]: 30
};
3- Object Destructuring ES6
let person = { name: "John", age: 30 };
let { name, age } = person;
console.log(name); // John
console.log(age); // 30
4- Method Shorthand in Object Literals ES6
let person = {
name: "John",
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // Hello, my name is John
5- UsingObject.assign()
for Cloning or Merging Objects
let person = { name: "John", age: 30 };
let clone = Object.assign({}, person);
let merged = Object.assign({}, person, { job: "Developer" });
6- UsingObject.freeze()
to Make an Object Immutable:
let person = { name: "John", age: 30 };
Object.freeze(person);
person.name = "Jane"; // This will not change the name property
7- UsingObject.entries()
and Object.fromEntries()
for Object Manipulation
let person = { name: "John", age: 30 };
let entries = Object.entries(person); // [['name', 'John'], ['age', 30]]
let newObj = Object.fromEntries(entries); // { name: 'John', age: 30 }
8- UsingObject.keys()
and Object.values()
to Get Arrays of Properties or Values:
let person = { name: "John", age: 30 };
let keys = Object.keys(person); // ['name', 'age']
let values = Object.values(person); // ['John', 30]
الدرس الثاني:
ليه ال Classes هتسهل حياتك؟
في عالم البرمجة، دايمًا بندور على طرق تخلي الكود بتاعنا أنضف وأسهل للفهم. وهنا بتظهر أهمية الـClasses في الجافا سكريبت. الـClass عبارة عن "قالب" أو "نموذج" بيساعدنا ننشئ objects متشابهين بسهولة. يعني بدل ما نعمل object لكل حاجة على حدة ونكرر الكود، نقدر نستخدم class واحد يعملهم كلهم.
ايه هو ال Constructor؟
الـConstructor ده جزء مهم جدًا في الـClass. ده الفانكشن اللي بيتنفذ أول ما نعمل object جديد من الـClass ده. هنا بنحط الخصائص الأساسية للـobject، وبنعرف قيمها الافتراضية. فمثلًا لو عندنا class اسمه "Car"، ممكن نحط في الـConstructor خصائص زي اللون والموديل.
ليه ال Classes احسن من ال Objects العادية؟
تنظيم أفضل: بدل ما تحط كل حاجة في object كبير وتتوه فيه، الـClasses بتخليك تقسم الكود بتاعك لحاجات صغيرة ومنظمة.
إعادة الاستخدام: لو عندك كذا object فيهم خصائص متشابهة، بدل ما تكرر نفس الكود في كل واحد، تقدر تعمل class واحد وتستخدمه لإنشاء كل الـobjects دي.
التوسع والتعديل: لو جيت تضيف خاصية جديدة أو تغير في الخصائص الحالية، بتكون العملية أسهل وأسرع لما تكون شغال بـClasses.
مثال عملي:
تخيل إنك بتعمل موقع لمعرض سيارات. بدل ما تعمل object لكل سيارة كده:
let car1 = { make: 'Toyota', model: 'Corolla', year: 2020 };
let car2 = { make: 'Honda', model: 'Civic', year: 2021 };
// وهكذا...
تقدر تعمل class واحد وتستخدمه لإنشاء كل السيارات:
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
}
let car1 = new Car('Toyota', 'Corolla', 2020);
let car2 = new Car('Honda', 'Civic', 2021);
// وهكذا...
بالطريقة دي، لو عايز تضيف خاصية جديدة زي اللون، تقدر تضيفها في الـClass مرة واحدة.
هنعمل مع بعض تمرين صغير على الـClasses والـConstructors. هنعمل class اسمها BankAccount
بتمثل حساب بنكي لمستخدم.
كل حساب بنكي هيكون عنده شوية properties: رقم الحساب، اسم صاحب الحساب، ورصيد الحساب اللي بيبدأ بصفر لو متحطش فيه فلوس. وهيكون عندنا كمان شوية methods: واحد للإيداع وواحد للسحب.
class BankAccount {
constructor(accountNumber, accountHolder, balance = 0) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = balance;
}
deposit(amount) {
if (amount > 0) {
this.balance += amount;
console.log(`You deposited $${amount}. New balance is $${this.balance}.`);
} else {
console.log("Can't deposit a negative amount.");
}
}
withdraw(amount) {
if (amount > this.balance) {
console.log("You can't withdraw that much.");
} else {
this.balance -= amount;
console.log(`You withdrew $${amount}. New balance is $${this.balance}.`);
}
}
}
// دلوقتي نقدر نعمل instance جديدة من ال BankAccount ونجرب ال methods:
const account = new BankAccount("123ABC", "Osama Soliman");
console.log(account.balance); // $0
account.deposit(200);
console.log(account.balance); // $200
account.withdraw(50);
console.log(account.balance); // $150
ده شكل Class الـ BankAccount
بعد ما ضفنا الـConstructor والـMethods.
الدرس الثالث:
لو بصينا للعالم البرمجة، هنلاقي إن في حاجات كتير بتتكرر. فمثلاً، لو عندنا كذا شكل هندسي، كلهم ليهم خصائص مشتركة زي المساحة والمحيط. علشان كده بنستخدم الـInheritance، يعني بنورث الخصائص دي من class لـ class تاني.
يعني لو عندنا class اسمها Shape
بتحتوي على الوظائف الأساسية لأي شكل، وعاوزين نعمل class جديد للمثلثات، ممكن نعملها بسهولة ونورث من الـShape
.
استخدام الكلمة الأساسية super
الكلمة الأساسية super بقى، دي بتستخدم علشان نقدر ننادي constructor بتاع الـparent class من جوه الـconstructor بتاع الـsubclass. يعني بنضمن إننا ما بننساش نحط الخصائص الأساسية اللي المفروض كل الأشكال تشترك فيها.
مثال على الـsuper
تخيل معايا كده، عندنا class للمثلثات بسيطة اسمها Triangle
:
class Triangle {
constructor(a, b) {
this.a = a;
this.b = b;
}
getArea() {
return 0.5 * this.a * this.b;
}
}
دلوقتي لو عايزين نعمل class جديدة للمثلثات بس اللي عندها لون، يعني ColorTriangle
، هنحتاج نورث كل حاجة من Triangle
ونضيف عليها اللون:
class ColorTriangle extends Triangle {
constructor(a, b, color) {
super(a, b); // هنا بننادي الـconstructor بتاع الـTriangle
this.color = color;
}
}
شايفين إزاي استخدمنا super علشان نتأكد إن الخصائص a
و b
بتاعة الـTriangle
اتعملت صح؟ ده بيساعدنا نحافظ على التنظيم ونضمن إن الـcode بتاعنا دايمًا نظيف وسهل نفهمه ونعدل عليه.
الـInheritance والكلمة الأساسية super دول أدوات قوية في عالم JavaScript تساعدك تكتب كود أكثر تنظيمًا وتسهل عليك عملية إعادة استخدام الكود. بالاستفادة من الـInheritance، بتقدر تبني تطبيقاتك بشكل أكثر كفاءة وتحافظ على قابلية التوسع والصيانة بشكل أفضل.
في النهاية، الـInheritance واستخدام super بيخليك قادر تعبر عن الهياكل التنظيمية للبيانات في برنامجك بشكل واضح ومحكم. وبكده تقدر تخلي كودك أنضف وأسهل للفهم لأي حد هيشتغل عليه بعدك أو حتى لما ترجعله بعد فترة.
مثال تطبيقي على super
لنفترض إننا عايزين نعمل نوع جديد من المثلثات بيظهر حالته النفسية، يعني class اسمه MoodTriangle
:
class MoodTriangle extends ColorTriangle {
constructor(a, b, color, mood) {
super(a, b, color); // بننادي constructor بتاع الـColorTriangle
this.mood = mood;
}
}
let myTriangle = new MoodTriangle(3, 4, 'blue', 'happy');
console.log(`This triangle feels ${myTriangle.mood} today!`);
نصائح لاستخدام الـInheritance بفاعلية
تجنب الوراثة العميقة: كلما زاد عدد المستويات في سلسلة الوراثة، كلما صعب تتبع وصيانة الكود. حاول تبقى الوراثة لمستوى أو اتنين إذا أمكن.
استخدام الـInheritance للتجميع الطبيعي: الوراثة فعالة جدًا لما تكون بتعبر عن علاقات طبيعية زي العلاقة بين الـCar
و ElectricCar
.
استخدم الـComposition عند اللزوم: في بعض الحالات، استخدام الـComposition بيكون أنسب من الـInheritance. يعني تحتفظ ب object من نوع تاني جوه ال object بدل ما تورثه.
الدرس الرابع:
يا سلام على الجمال لما نلاقي حاجات في البرمجة بتسهل علينا الحياة وتخلي الكود أكثر تنظيم! النهاردة هنتكلم عن مفهومين مهمين جدًا في JavaScript وهما الـStatic Properties والـStatic Methods. دول بيخلونا نعرف نتعامل مع خصائص ووظائف مرتبطة بالـclass نفسها مش بكل instance من الـclass. يعني هنتكلم على حاجات بتتعامل مع الصورة الكبيرة مش التفاصيل الصغيرة اللي بتتغير من object للتاني.
Static Properties
تعالوا نشوف مثال يوضح الفكرة دي:
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
static brand = "Toyota"; // دي خصيصة static، يعني متعلقة بال class كله مش ب instance
}
console.log(Car.brand); // "Toyota"
const myCar = new Car("Corolla", 2020);
console.log(myCar.brand); // undefined
هنا brand هي خاصية static متعلقة بالـclass نفسه، مش بالـinstances. فلما نحاول نوصل لـbrand من خلال instance زي myCar هنلاقيها مش موجودة، لأنها موجودة فقط في الـclass نفسه.
Static Methods
الـStatic Methods دي زي الـStatic Properties بالظبط، بس هي طرق (وظائف) مش خصائص. يعني بتتنادى من الـclass نفسه، مش من الـinstances.
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
static info() {
return "This is a car class.";
}
}
console.log(Car.info()); // "This is a car class."
const myCar = new Car("Toyota Corolla", 2020);
console.log(myCar.info()); // Error: myCar.info is not a function
زي ما شوفنا في المثال، لو حاولنا ننادي info من خلال instance هتظهر لنا رسالة خطأ لأن info موجودة فقط في الـclass.
استخدامات الـ Static Methods
الـStatic Methods بيتم استخدامها كتير لما عايزين ننظم وظائف متعلقة ببعض في class واحد، زي الوظائف اللي بتقارن بين عربيات أو بتعمل حسابات معينة.
مثال:
class CarUtils {
static compareYears(car1, car2) {
return car1.year === car2.year;
}
static isOlder(car1, car2) {
return car1.year < car2.year;
}
}
const car1 = { model: "Toyota Corolla", year: 2020 };
const car2 = { model: "Honda Civic", year: 2018 };
console.log(CarUtils.compareYears(car1, car2)); // false
console.log(CarUtils.isOlder(car1, car2)); // true
كمان ممكن نستخدمها لعمل factory methods، يعني طرق بتساعدنا نصنع instances جديدة من الـclass.
class Car {
static createElectricCar(model, year) {
const electricCar = new Car(model, year);
electricCar.type = "Electric";
return electricCar;
}
}
const tesla = Car.createElectricCar("Tesla Model 3", 2021);
console.log(tesla); // { model: "Tesla Model 3", year: 2021, type: "Electric" }
الـStatic Properties والـStatic Methods دي أدوات قوية جدا في JavaScript تساعدنا ننظم الكود بشكل أحسن ونوفر وظائف عامة ممكن الكل يستفيد منها بدون ما يحتاج يعمل instance من الـclass.
الدرس الخامس:
الـGetters وSetters. دي طرق بتخلينا نتحكم في القيم بشكل أدق وأذكى من خلال تعريف كيفية جلب القيم (get) وتعديلها (set).
Getters
Getters دي function بنستخدمها علشان نجيب قيمة property من object بطريقة محددة نحن محددينها. يعني هي بتخليك تقدر تعرف تشغل كود لما تطلب قيمة الproperty دي.
مثال على Getter:
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
get age() {
const currentYear = new Date().getFullYear();
return currentYear - this.year;
}
}
const myCar = new Car("Toyota Corolla", 2015);
console.log(myCar.age); // بتحسب عدد السنين من 2015 لحد السنة الحالية
لاحظ ان هنا الـgetter اسمه age
بيحسب العمر الافتراضي للعربية من سنة الصنع لحد السنة الحالية وكأنه property عادية مش method.
Setters
Setters بقى، دي function بنستخدمها علشان نغير قيمة property في object بطريقة محددة. يعني بيخليك تقدر تعرف كود بيشتغل لما تغير القيمة دي.
مثال على Setter:
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
set updateYear(newYear) {
if (newYear > 0) {
this.year = newYear;
} else {
throw new Error("Year must be positive.");
}
}
}
const myCar = new Car("Toyota Corolla", 2015);
console.log(myCar.year); // 2015
myCar.updateYear = 2020; // بنغير سنة الإنتاج لـ 2020
console.log(myCar.year); // 2020
هنا الـsetterupdateYear
بيتأكد إن السنة الجديدة موجبة قبل ما يعدلها في الobject.
استخدامات متقدمة للـ Getters والـ Setters
الجميل في الـGetters وSetters إنك ممكن تستخدمهم في مواقف مختلفة ليها علاقة بتعديل بيانات الـobjects بطريقة آمنة ومرنة. زي ما هنشوف في الـUser class:
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(newName) {
const [first, last] = newName.split(" ");
this.firstName = first;
this.lastName = last;
}
}
const user = new User("Osama", "Soliman");
console.log(user.fullName); // "Osama Soliman"
user.fullName = "Osama Soliman";
console.log(user.firstName); // "Osama"
console.log(user.lastName); // "Soliman"
في الأمثلة دي، الـGetters والـSetters يخلوك تتحكم في البيانات بطريقة ذكية وأمانة. مثلاً، لو عندك بيانات مهمة وعايز تضمن إن القيم اللي بتتحط فيها صحيحة ومناسبة، تقدر تستخدم الـSetters لتحقيق ده. ولو عايز تحسب قيم معينة من البيانات الأساسية بتاعتك بطريقة مخصوصة، الـGetters هيبقى حل مثالي.
خلاصة
الـGetters والـSetters دول أدوات قوية في JavaScript بيتيحولك:
التحكم في قراءة القيم: عن طريق الـGetters، تقدر تحدد إزاي البيانات تُقرأ وحتى تحسب بشكل تلقائي من الخصائص الأخرى.
التحكم في كتابة القيم: عن طريق الـSetters، تقدر تتأكد من صحة البيانات الجديدة قبل ما تعدل الخصائص.
الدرس السادس:
الـPublic وPrivate Class Fields. دي مفاهيم بتساعدنا نتحكم في الوصول لخصائص الـclass بطريقة أكثر أمانًا ونظامًا.
Public Class Fields (الخصائص العامة)
لما بنتكلم عن Public Class Fields، بنقصد الخصائص اللي ممكن توصل ليها من أي مكان في الكود، وبتتعرف مباشرة في الـclass نفسه مش في الـconstructor.
مثال على Public Field:
class Car {
wheels = 4; // هذه خاصية عامة
constructor(model, year) {
this.model = model;
this.year = year;
}
}
const myCar = new Car("Toyota Corolla", 2020);
console.log(myCar.wheels); // 4
في المثال ده، كل Car هيكون عندها wheels بقيمة 4. ده بيسهل علينا ندي قيم افتراضية للخصائص من غير ما نحتاج نتعامل مع الـconstructor.
Private Class Fields (الخصائص الخاصة)
الـPrivate Class Fields دي عكس الـPublic تمامًا. بيتم تعريفها بحيث لا يمكن الوصول إليها إلا من داخل الـclass نفسه.
مثال على Private Field:
class Car {
#mileage; // هذه خاصية خاصة
constructor(model, year, mileage) {
this.model = model;
this.year = year;
this.#mileage = mileage;
}
getMileage() {
return this.#mileage;
}
setMileage(newMileage) {
if (newMileage >= 0) {
this.#mileage = newMileage;
} else {
throw new Error("Mileage must be positive.");
}
}
}
const myCar = new Car("Toyota Corolla", 2020, 50000);
console.log(myCar.getMileage()); // 50000
myCar.setMileage(60000);
console.log(myCar.getMileage()); // 60000
في المثال ده، #mileage مش هتقدر توصل ليها من بره الـclass. بس يمكنك استخدام getters وsetters علشان تدير القيم بطريقة محكمة وآمنة.
خلاصة
الفرق بين Public و Private Class Fields ده جوهري جدًا في تصميم البرمجيات الكائنية الوجهة. الـPublic Fields بيتم استخدامهم لما بنحتاج نتشارك بيانات بسهولة عبر الكائنات المختلفة. أما الـPrivate Fields فبيوفروا طبقة حماية للبيانات يعني محدش يقدر يغيرهم إلا من خلال الوظائف المحددة داخل الـclass.
الاستخدام الصحيح للخصائص والطرق العامة والخاصة بيزود من كفاءة البرمجيات وبيسهل صيانتها وتطويرها مع الزمن. حاول دايمًا تفكر كويس في اختياراتك لما بتصمم الـclasses بتاعتك، واختار النوع المناسب من الخصائص والوظائف لكل مهمة.
الدرس السابع:
زي ما عندنا Private Properties ، كمان عندنا Private Methods مينفعش نستدعيها من بره الـclass، وده بيساعد في إخفاء تفاصيل التنفيذ الداخلية للـclass.
مثال على Private Method:
class Car {
#startEngine() {
console.log("Engine started");
}
drive() {
this.#startEngine();
console.log("Car is driving");
}
}
const myCar = new Car();
myCar.drive(); // "Engine started" ثم "Car is driving"
في المثال ده، الطريقة #startEngine بتشتغل من جوه الـclass بس، ومحدش بره الـclass يقدر يناديها مباشرةً. ده بيسمح لنا نخفي تفاصيل معينة عن التنفيذ ونحمي وظائف الـclass من التعديلات غير المقصودة أو الاستخدام الخاطئ.
استخدام Private Methods كمان مهم لإنه بيسمح لك تحفظ تفاصيل الكود التنفيذية داخل الـclass، بحيث إن التفاصيل دي ميتعرضش للتغيير من الخارج، وبالتالي تضمن استقرار وأمان البرنامج.
الدرس الثامن:
الـStatic Initialization Blocks. دي ميزة بتسمحلنا ننفذ كود معين مرة واحدة بس، لما الـclass بتاعنا يتحمل لأول مرة.
ما هي الـ Static Initialization Blocks؟
Static Initialization Blocks دي عبارة عن بلوك من الكود بيتشغل مرة واحدة فقط، لما الـclass بيتحمل في الذاكرة لأول مرة. ده بيسمحلنا نعمل تحضيرات معينة أو ننفذ إعدادات مبدئية قبل ما أي instances من الـclass تُنشأ.
مثال على Static Initialization Block:
class Car {
static numCars = 0; // ده عداد لعدد العربيات اللي تم تحميلها
static {
// هنا بنزود العداد بواحد كل ما الـclass يتحمل
this.numCars += 1;
}
constructor(model, year) {
this.model = model;
this.year = year;
}
}
const myCar = new Car("Toyota Corolla", 2020);
console.log(Car.numCars); // 1
في المثال ده، static initialization block استخدمناه علشان نزود عدد العربيات بواحد كل ما الـclass يتحمل. يعني كل ما نعمل new Car()
، العداد هيزيد بواحد.
ليه نستخدم Static Initialization Blocks؟
استخدام الـStatic Initialization Blocks مفيد في حالات كتيرة، زي:
ء✅ إعداد قيم ابتدائية للـstatic properties.
ء✅ تنفيذ كود تحقق أو إعداد قبل أي instances تُنشأ.
ء✅ تحميل موارد ثقيلة أو إعداد اتصالات شبكية قبل تشغيل البرنامج.
خلاصة
Static Initialization Blocks في JavaScript دي أداة قوية بتساعدنا ندير تحميل الـclasses بطريقة فعالة ونظامية. استخدامها بيضمن إننا نعمل الإعدادات اللازمة قبل ما نبدأ نستخدم الـclass، وبكده بنكون متأكدين إن كل حاجة جاهزة ومظبوطة.
في دروسنا الجاية، هنستمر في استكشاف مواضيع أعمق في عالم JavaScript. فخليك متابع معانا، ولو عندك أي سؤال أو تعليق، ما تترددش في طرحه. شكرًا لمتابعتكم ولحد اللقاء في الدرس الجاي!