JavaScript: الفئات والوحدات في JavaScript
مع نمو المشاريع، تحتاج أكثر من دوال ومتغيرات متناثرة — تحتاج هيكل كود منظم. الفئات توفر أنماط الكائنية، والوحدات توفر عزل الكود على مستوى الملفات. معاً، يشكلان أساس مشاريع JS الحديثة.
1. صيغة الفئة (Class)
الفئة هي قالب لإنشاء كائنات — سكر صناعي فوق دوال المُنشئ.
<script>
class ClassName {
constructor(params) {
this.property = params;
}
method() {
// ...
}
}
</script>
▶ مثال
// مثال على الكود في هذا القسم
const message = "مرحبا بالعالم!";
console.log(message);
هذا أبسط مثال; الأقسام التالية ستشرح بالتفصيل.### ▶ مثال: إنشاء فئة Student
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>
<script>
const output = document.getElementById('output');
class Student {
constructor(name, age, grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
introduce() {
return 'اسمي ' + this.name + '، عمري ' + this.age + ' سنة، في الصف ' + this.grade + '.';
}
study(subject) {
return this.name + ' يدرس ' + subject + '.';
}
}
const s1 = new Student('أليس', 12, 'السادس');
const s2 = new Student('بوب', 11, 'الخامس');
output.textContent = s1.introduce() + '\n' + s2.introduce() + '\n' + s1.study('الرياضيات');
</script>
class هو في الأساس سكر صناعي فوق دوال المُنشئ + البروتتويبات. new Student() يعمل بنفس طريقة new function Student() القديمة، لكن الصيغة أنظف وأكثر حدسية.
2. الخصائص والطرق
(1) خصائص وطرق المثيل
الخصائص والطرق المحددة بـ this.xxx داخل constructor تنتمي للمثيل — كل كائن يحصل على نسخته الخاصة.
(2) الطرق الثابتة (Static Methods)
الطرق المحددة بكلمة static تنتمي للفئة نفسها، وليس للمثيلات. استدعِها عبر ClassName.method().
(3) Getters و Setters
استخدم كلمتي get و set لتحديد "خصائص افتراضية" — دوال تُنفذ تلقائياً عند القراءة/الكتابة.
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc; direction: ltr; text-align: left;"></div>
<script>
const output = document.getElementById('output');
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return this.firstName + ' ' + this.lastName;
}
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
}
const p = new Person('جون', 'دو');
output.textContent = 'fullName: ' + p.fullName + '\n';
p.fullName = 'جين سميث';
output.textContent += 'بعد التغيير: ' + p.fullName;
</script>
▶ مثال: الطرق الثابتة و Getters/Setters
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc; direction: ltr; text-align: left;"></div>
<script>
const output = document.getElementById('output');
class Circle {
static count = 0;
constructor(radius) {
this.radius = radius;
Circle.count++;
}
get area() {
return Math.PI * this.radius * this.radius;
}
get diameter() {
return this.radius * 2;
}
set diameter(value) {
this.radius = value / 2;
}
static createUnit() {
return new Circle(1);
}
}
const c1 = new Circle(5);
const c2 = new Circle(10);
const c3 = Circle.createUnit();
output.textContent = 'دائرة بنصف قطر 5:\n';
output.textContent += ' المساحة: ' + c1.area.toFixed(2) + '\n';
output.textContent += ' القطر: ' + c1.diameter + '\n';
output.textContent += ' تعيين القطر إلى 20:\n';
c1.diameter = 20;
output.textContent += ' نصف القطر الجديد: ' + c1.radius + '\n\n';
output.textContent += 'إجمالي الدوائر المنشأة: ' + Circle.count + '\n';
output.textContent += 'نصف قطر الدائرة الوحدة: ' + c3.radius;
</script>
3. الوراثة
extends تتيح لفئة الابن أن ترث خصائص وطرق من فئة الأب. super يستدعي مُنشئ الأب أو طرقه.
▶ مثال: عرض الوراثة
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc; direction: ltr; text-align: left;"></div>
<script>
const output = document.getElementById('output');
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
return this.name + ' يقول: ' + this.sound;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name, 'نباح');
this.breed = breed;
}
fetch(item) {
return this.name + ' أحضر ' + item;
}
}
class Cat extends Animal {
constructor(name, indoor) {
super(name, 'مواء');
this.indoor = indoor;
}
purr() {
return this.name + ' يخرخر...';
}
}
const dog = new Dog('ريكس', 'شيبا إينو');
const cat = new Cat('شارب', true);
output.textContent = dog.speak() + '\n';
output.textContent += dog.fetch('فريسبي') + '\n';
output.textContent += cat.speak() + '\n';
output.textContent += cat.purr();
</script>
constructor فئة الابن، يجب استدعاء super() قبل استخدام this — لأنه حتى يتم تهيئة الأب، this للابن غير موجود. هذه القاعدة الأكثر سهولة للنسيان عند تعلم الوراثة.
4. وحدات ES (ES Modules)
الوحدات هي وحدة تنظيم الكود في JS. وحدة واحدة = ملف واحد. المتغيرات داخل الوحدة خاصة بشكل افتراضي — فقط العناصر المُصدّرة صراحة يمكن الوصول إليها خارجياً.
(1) التصدير/الاستيراد المُسمى
<script>
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }
// app.js
import { PI, add } from './math.js';
</script>
(2) التصدير/الاستيراد الافتراضي
<script>
// logger.js
export default function log(msg) { console.log(msg); }
// app.js
import log from './logger.js';
</script>
(3) استخدام الوحدات في HTML
ببساطة أضف type="module" إلى وسم script.
<script type="module">
import { add } from './math.js';
console.log(add(1, 2));
</script>
▶ مثال: محاكاة وحدة ملف واحد (وحدة مضمنة)
في المشاريع الحقيقية، يجب تقسيم الوحدات إلى ملفات منفصلة. هنا نحاكيها داخل ملف HTML واحد باستخدام type="module".
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>
<script type="module">
const output = document.getElementById('output');
const calculator = {
add(a, b) { return a + b; },
subtract(a, b) { return a - b; },
multiply(a, b) { return a * b; },
divide(a, b) { return b !== 0 ? a / b : 'لا يمكن القسمة على صفر'; }
};
const formatter = {
currency(value) { return '$' + value.toFixed(2); },
percent(value) { return (value * 100).toFixed(1) + '%'; }
};
const r1 = calculator.add(10, 20);
const r2 = calculator.multiply(5, 4);
const r3 = calculator.divide(10, 3);
output.textContent = '10 + 20 = ' + r1 + '\n';
output.textContent += '5 × 4 = ' + r2 + '\n';
output.textContent += '10 ÷ 3 = ' + formatter.currency(r3) + '\n';
output.textContent += '0.85 → ' + formatter.percent(0.85);
// في مشروع حقيقي ستقسم هكذا:
// calculator.js → export { calculator }
// formatter.js → export { formatter }
// main.js → import { calculator } from './calculator.js'
// import { formatter } from './formatter.js'
</script>
❓ أسئلة شائعة
new (لا يمكن تشغيلها كدوال عادية)، الطرق غير قابلة للعد، والوضع الصارم مُفعّل افتراضياً. الصيغة أنظف، لذا تُوصى بالفئات.import و require؟import هو صيغة وحدات ES — يُحلل ثابتاً وقت التجميع. require هو CommonJS (Node.js) — يُحمّل ديناميكياً وقت التشغيل. المتصفحات تدعم أصلاً فقط وحدات ES. import يجب أن يكون في المستوى الأعلى؛ require يمكن أن يكون في أي مكان.export default App; export const utils = {};. استورد بـ: import App, { utils } from './module.js'.📖 ملخص
classهو سكر صناعي فوق المُنشئات؛constructorهو المُهيئ، والطرق تُحدد على البروتتويب- الطرق
staticتنتمي للفئة، وليس للمثيلات — مثالية لطرق الأدوات والمصانع get/setيحددان خصائص افتراضية تعتراض القراءات/ال الكتابة للتحقق أو الحسابextendsتتيح الوراثة؛constructorالابن يجب أن يستدعيsuper()أولاً- وحدات ES تستخدم
export/importلعزل الكود وإعادة استخدامه؛type="module"تفعّلها في HTML - الوحدات تنظم الكود حسب الميزة، تتيح التحميل الكسول، وتمنع تلوث مساحة الأسماء العامة
📝 تمارين
- أساسي: أنشئ فئة
Rectangleبخاصيتيwidthوheight، بالإضافة إلى طريقتيgetArea()وgetPerimeter(). - متوسط: أضف
get area()getter وset area(value)setter إلىRectangle(تعيين المساحة يعدّل العرض/الارتفاع بشكل متناسب). ثم أنشئ فئة فرعيةSquareتمتدRectangle. - تحدي: صمم فئة
EventManagerبطرقتَيon(event, callback)وoff(event, callback)وemit(event, data)لمحاكاة نظام أحداث بسيط (تلميح: استخدم كائن يُعيّن الأحداث إلى مصفوفات استدعاءات).