الدليل الشامل للخصائص والدوال الثابتة (Static Members) في TypeScript: من الأساسيات إلى هندسة البرمجيات المتقدمة
مقدمة عن الخصائص والدوال الثابتة
نظرة عامة على الخصائص والدوال الثابتة وأهميتها في TypeScript
في TypeScript، تعتبر الخصائص والدوال الثابتة جزءاً أساسياً من تصنيف الكائنات وهندسة الفئات (Classes). الخصائص الثابتة (Static Properties) هي خصائص ترتبط بالفئة نفسها ككيان مستقل بدلاً من ارتباطها بنسخة أو كائن معين (Instance). أما الدوال الثابتة (Static Methods) فهي وظائف برمجية تُستدعى مباشرة عبر اسم الفئة دون الحاجة إلى إنشاء كائن جديد باستخدام المعامل new.
الخصائص والدوال الثابتة تساهم في تحسين تنظيم الكود وتسهيل إدارة الموارد والكائنات عبر توفير مساحة مركزية مشتركة للبيانات والوظائف المساعدة. في هذا الدرس التدريبي من دورة TypeScript من الصفر إلى الاحتراف (الدرس #15)، سنستعرض بالتفصيل كيفية تعريف الخصائص والدوال الثابتة، وقواعد استخدامها، وتفاعلها مع الوراثة والحالة، وصولاً إلى أنماط التصميم المتقدمة.
تعريف الخصائص الثابتة
- طريقة التعريف: لتعريف خاصية ثابتة، نضع الكلمة المفتاحية
staticقبل اسم الخاصية داخل جسم الفئة، مع تحديد نوع البيانات وقيمتها الابتدائية إن وجدت. - استخدامات الخاصية الثابتة: تُستخدم لتخزين الثوابت العامة المرتبطة بمجال الفئة (مثل المعاملات الرياضية أو إعدادات النظام)، والاحتفاظ بالحالة المشتركة بين جميع نسخ الكائنات (مثل عداد الكائنات المنشأة)، وتخزين الذاكرة المؤقتة (Cache).
تعريف الدوال الثابتة
- طريقة التعريف: تُعرّف الدالة الثابتة بوضع الكلمة المفتاحية
staticقبل اسم الدالة. يمكن للدوال الثابتة أن تقبل معاملات، وتُرجع قيماً، وتطبق قيود الأنواع الصارمة كأي دالة عادية في TypeScript. - استخدامات الدالة الثابتة: إنشاء دوال مساعدة (Utility Functions) لا تعتمد على حالة الكائن الفردي، وإنشاء مصانع الكائنات (Factory Methods)، وإجراء العمليات الحسابية والتحويلات الهيكلية للبيانات.
فوائد استخدام الخصائص والدوال الثابتة
- تحسين تنظيم الكود وتجميع المسؤوليات: حصر الدوال والأدوات المساعدة ذات الصلة داخل الفئة المعنية بها (مثل
MathUtilsأوDateTimeUtils) بدلاً من نشر دوال عالمية تلوث النطاق العام (Global Scope). - تعزيز إعادة الاستخدام والكفاءة البرمجية: استدعاء الوظائف مباشرة من اسم الفئة دون استهلاك موارد الذاكرة لإنشاء كائنات غير ضرورية.
- تحسين الأداء وإدارة الذاكرة (Memory Optimization): تُحفظ الأعضاء الثابتة مرة واحدة فقط على دالة البناء (Constructor Function) في الذاكرة، ولا يتم تكرار حجز مساحات لها مع كل كائن جديد.
أمثلة عملية تأسيسية
مثال على فئة حاسبة رياضية بدوال ثابتة
في العمليات الحسابية، لا نحتاج لتخزين حالة داخل كائن؛ لذا فإن الدوال الثابتة هي الخيار المثالي:
class Calculator {
// خاصية ثابتة لثابت رياضي
static readonly PI: number = 3.14159265359;
// دوال ثابتة تقبل معاملات وتعيد نتائج حسابية
static sum(a: number, b: number): number {
return a + b;
}
static multiply(a: number, b: number): number {
return a * b;
}
static calculateCircleArea(radius: number): number {
return this.PI * radius * radius;
}
}
// الاستدعاء المباشر دون الحاجة لـ new Calculator()
console.log(Calculator.sum(10, 5)); // الناتج: 15
console.log(Calculator.multiply(4, 3)); // الناتج: 12
console.log(Calculator.calculateCircleArea(10)); // الناتج: 314.159...الدمج بين الأعضاء الثابتة وأعضاء النسخ (Static vs Instance Members)
يوضح المثال التالي كيف يمكن لفئة واحدة أن تحتوي على خصائص ودوال خاصة بنسخ الكائنات الفردية، إلى جانب خصائص ودوال عامة مشتركة على مستوى الفئة ككل:
class Person {
// خاصية ثابتة مشتركة لتسجيل نوع الكيان
static species: string = "Homo sapiens";
// خاصية ثابتة تعمل كمُحصّل (Getter)
static get category(): string {
return "كائن بشري";
}
// دالة ثابتة عامة
static describeSpecies(): void {
console.log(`النوع البشري يندرج تحت تصنيف: ${this.species}`);
}
// خصائص النسخ (Instance Properties)
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// دالة النسخة (Instance Method)
get fullName(): string {
return `${this.name} (${this.age} عاماً)`;
}
}
// 1. استخدام الأعضاء الثابتة مباشرة عبر اسم الفئة:
console.log(Person.species); // Homo sapiens
console.log(Person.category); // كائن بشري
Person.describeSpecies(); // استدعاء دالة ثابتة
// 2. استخدام أعضاء النسخ بعد الإنشاء:
const person1 = new Person("محمد", 28);
console.log(person1.fullName); // محمد (28 عاماً)
// ❌ تنبيه هام في TypeScript:
// console.log(person1.species);
// خطأ ترجمة: Property 'species' is a static member of type 'Person'فهم الخصائص الثابتة (Static Properties) بعمق
الخصائص الثابتة هي خصائص مرتبطة ببنية الفئة نفسها بدلاً من الكائنات المشتقة منها. في JavaScript و TypeScript، تُخزن هذه الخصائص كحقول مباشرة على كائن دالة البناء (Constructor Function Object)، ولا تظهر داخل كائن النموذج الأولي (prototype) الخاص بالفئة.
تختلف لغة TypeScript عن لغات مثل Java أو C# في نقطة محورية: يمنع TypeScript الوصول إلى الخصائص والدوال الثابتة من خلال نسخ الكائنات (Instances). محاولة كتابة instance.staticProperty ستنتج خطأ ترجمة صريحاً، مما يمنع اللبس والغموض البرمجي:
class ConfigurationManager {
public static environment: string = "production";
public static timeoutMs: number = 5000;
}
// ✅ الأسلوب الصحيح في TypeScript:
console.log(ConfigurationManager.environment); // production
const config = new ConfigurationManager();
// ❌ خطأ في TypeScript:
// console.log(config.environment);
// Property 'environment' is a static member of type 'ConfigurationManager'قواعد وصياغة الدوال الثابتة (Static Methods)
تخضع الدوال الثابتة في TypeScript لمجموعة من القواعد الصارمة التي تضمن تكامل الشيفرة وسلامة المنطق البرمجي:
- سياق الكلمة المفتاحية
this: داخل الدالة الثابتة، تشير الكلمة المفتاحيةthisإلى دالة بناء الفئة نفسها (Class Constructor)، وليس إلى أي كائن حي. هذا يعني أنه يمكنك استدعاء دوال ثابتة أخرى أو قراءة خصائص ثابتة عبرthis.staticMethod(). - استحالة الوصول إلى أعضاء النسخ مباشرة: لا يمكن للدالة الثابتة قراءة أو تعديل الخصائص العادية مثل
this.nameلأن النسخة الفردية غير موجودة في هذا السياق؛ إلا إذا تم تمرير الكائن كمعامل صريح للدالة الثابتة. - دعم الأنواع والمعاملات: تدعم الدوال الثابتة كامل قدرات TypeScript مثل الأنواع العامة (Generics)، المعاملات الاختيارية، المعاملات الافتراضية، ومعاملات الباقي (Rest Parameters).
class StringHelper {
// دالة ثابتة عامة تدعم الأنواع والمعاملات الافتراضية
public static truncate(text: string, maxLength: number = 30, suffix: string = "..."): string {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength) + suffix;
}
public static capitalize(text: string): string {
if (!text) return "";
return text.charAt(0).toUpperCase() + text.slice(1);
}
}
console.log(StringHelper.truncate("مقدمة إلى TypeScript وبيئة العمل من الصفر", 20));
console.log(StringHelper.capitalize("typescript")); // Typescriptتعديل الخصائص والدوال الثابتة وإدارة الحالة
تُعد الخصائص والدوال الثابتة جزءاً لا يتجزأ من تعريف الفئة نفسها، وليست مرتبطة بأي كائن يتم إنشاؤه منها. هذا يعني أن أي تعديل يُجرى على قيمة الخاصية الثابتة هو تعديل عالمي موحد (Global Mutation) تشعر به كافة أجزاء التطبيق والكائنات التي تعتمد على تلك الفئة.
لنتعرف على كيفية إدارة الحالة المشتركة عبر تطبيق نمط “عداد الكائنات النشطة” (Instance Tracking):
class ActiveSession {
// خاصية ثابتة خاصة لتخزين عدد الجلسات المفتوحة
private static _activeCount: number = 0;
public readonly sessionId: string;
public readonly createdAt: Date;
constructor(id: string) {
this.sessionId = id;
this.createdAt = new Date();
// زيادة العداد المشترك في كل مرة يُنشأ فيها كائن جديد
ActiveSession._activeCount++;
}
// دالة ثابتة للوصول الآمن إلى العداد
public static get activeCount(): number {
return ActiveSession._activeCount;
}
// إنهاء الجلسة وخفض العداد
public terminate(): void {
console.log(`تم إنهاء الجلسة: ${this.sessionId}`);
ActiveSession._activeCount--;
}
}
console.log(`الجلسات قبل البدء: ${ActiveSession.activeCount}`); // 0
const session1 = new ActiveSession("SES-001");
const session2 = new ActiveSession("SES-002");
const session3 = new ActiveSession("SES-003");
console.log(`إجمالي الجلسات النشطة: ${ActiveSession.activeCount}`); // 3
session2.terminate();
console.log(`الجلسات النشطة بعد الإنهاء: ${ActiveSession.activeCount}`); // 2الخصائص والدوال الثابتة والميراث (Static Inheritance)
يُعد الميراث (Inheritance) أحد الركائز الأساسية للبرمجة كائنية التوجه (OOP)، حيث يتيح للفئات الفرعية (Subclasses) وراثة الخصائص والأساليب من الفئة الأساسية (Base Class). في لغة JavaScript الحديثة وTypeScript، تتورث الأعضاء الثابتة أيضاً بشكل طبيعي عبر سلسلة النماذج الأولية لدوال البناء:
class DatabaseConnector {
public static defaultPort: number = 5432;
protected static connectionPoolSize: number = 10;
public static getConnectionString(): string {
return `server=localhost;port=${this.defaultPort};pool=${this.connectionPoolSize}`;
}
}
class PostgresConnector extends DatabaseConnector {
// يمكن للفئة المشتقة إعادة تعريف الخاصية الثابتة
public static override defaultPort: number = 5433;
// دالة ثابتة خاصة بالفئة الابن تستند إلى الدالة الموروثة عبر super
public static getSpecializedConnection(): string {
return `[Postgres Engine] ${super.getConnectionString()}`;
}
}
// 1. الفئة الأب تحتفظ بقيمتها:
console.log(DatabaseConnector.defaultPort); // 5432
console.log(DatabaseConnector.getConnectionString()); // port=5432
// 2. الفئة الابن ورثت الأعضاء مع تطبيق التعديلات:
console.log(PostgresConnector.defaultPort); // 5433
console.log(PostgresConnector.getSpecializedConnection()); // [Postgres Engine] server=...port=5432...تسمح الكلمة المفتاحية super.methodName() داخل الدوال الثابتة في الفئات المشتقة باستدعاء الدالة الثابتة للفئة الأم، مما يحافظ على التماسك المعماري والتسلسل الهرمي للشيفرة.
خصائص ودوال ثابتة متقدمة: كتل التهيئة وأنماط التصميم
1. كتل التهيئة الثابتة (Static Initialization Blocks)
بدءاً من إصدار TypeScript 4.4 والإصدارات الأحدث المتوافقة مع معايير ECMAScript الحديثة، يمكنك كتابة كتل برمجية متقدمة تُعرف بـ static { ... }. تتيح لك هذه الكتل تنفيذ منطق تهيئة معقد، واستخدام الشروط، وتطبيق عبارات try...catch لحساب قيم الخصائص الثابتة عند تحميل الفئة لأول مرة في الذاكرة:
class AppEnvironment {
public static apiUrl: string;
public static isProduction: boolean;
// كتلة التهيئة الثابتة تنفذ تلقائياً لمرة واحدة عند تحميل الكلاس
static {
try {
const rawEnv = "production"; // محاكاة قراءة متغيرات البيئة
this.isProduction = rawEnv === "production";
this.apiUrl = this.isProduction
? "https://api.code-elta6ur.com/v1"
: "http://localhost:3000/v1";
} catch (error) {
this.apiUrl = "http://localhost:3000/fallback";
this.isProduction = false;
}
}
}
console.log(AppEnvironment.apiUrl); // https://api.code-elta6ur.com/v1
console.log(AppEnvironment.isProduction); // true2. نمط التصميم المفرد (Singleton Pattern)
يُعد نمط Singleton أحد أشهر أنماط التصميم التي تعتمد كلياً على الخصائص والدوال الثابتة؛ حيث يضمن إنشاء نسخة واحدة فقط من الفئة طوال فترة تشغيل التطبيق، مع منع أي إنشاء خارجي عبر جعل الـ constructor خاصاً (private):
class DatabaseManager {
// متغير ثابت لحفظ النسخة الوحيدة
private static instance: DatabaseManager | null = null;
// جعل المنشئ خاصاً لمنع استدعاء new من خارج الفئة
private constructor(private connectionString: string) {
console.log("تم تأسيس الاتصال بقاعدة البيانات لأول مرة.");
}
// دالة ثابتة توفر نقطة وصول عالمية للنسخة الوحيدة
public static getInstance(): DatabaseManager {
if (!DatabaseManager.instance) {
DatabaseManager.instance = new DatabaseManager("mongodb://cluster.internal:27017");
}
return DatabaseManager.instance;
}
public query(sql: string): void {
console.log(`تنفيذ الاستعلام: ${sql}`);
}
}
// الاستخدام:
const db1 = DatabaseManager.getInstance(); // ينشئ النسخة لأول مرة
const db2 = DatabaseManager.getInstance(); // يعيد النسخة المحفوظة مسبقاً
console.log(db1 === db2); // true (كلا المتغيرين يشيران إلى نفس الكائن بالذاكرة)
db1.query("SELECT * FROM users");3. نمط دوال المصنع الثابتة (Static Factory Method Pattern)
بدلاً من إنشاء دوال بناء معقدة متعددة الحالات، توفر دوال المصنع الثابتة أسماءً وصفية وواضحة لإنشاء الكائنات بصيغ مختلفة وبأمان نوعي تام:
class UserProfile {
private constructor(
public readonly id: string,
public readonly username: string,
public readonly role: "ADMIN" | "EDITOR" | "GUEST"
) {}
// دالة مصنع ثابتة لإنشاء مستخدم مدير
public static createAdmin(username: string): UserProfile {
return new UserProfile(`ADM-${Date.now()}`, username, "ADMIN");
}
// دالة مصنع ثابتة لإنشاء زائر مؤقت
public static createGuest(): UserProfile {
return new UserProfile(`GST-${Math.random()}`, "زائر مجهول", "GUEST");
}
// دالة مصنع لإنشاء الكائن من بيانات JSON خارجية
public static fromJSON(jsonString: string): UserProfile {
const data = JSON.parse(jsonString);
return new UserProfile(data.id, data.username, data.role);
}
}
const adminUser = UserProfile.createAdmin("محمد الشامي");
const guestUser = UserProfile.createGuest();
console.log(adminUser.role); // ADMIN
console.log(guestUser.username); // زائر مجهولأفضل الممارسات والأخطاء الشائعة وحلولها
عند بناء الأنظمة بالاعتماد على الأعضاء الثابتة، قد يقع المطور في أخطاء تؤدي إلى صعوبة اختبار الكود أو تلوث الذاكرة. نلخص أفضل الممارسات وطرق معالجة المشكلات فيما يلي:
- تجنب استخدام الأعضاء الثابتة كحالة عالمية متغيرة (Avoid Mutable Global State): الإفراط في استخدام المتغيرات الثابتة القابلة للتعديل يجعل اختبار الوحدات (Unit Testing) صعباً للغاية، لأن الاختبارات المختلفة قد تعدل نفس المتغير فيتأثر سلوك الاختبار التالي. استخدم
readonlyمع الخصائص الثابتة قدر الإمكان. - عدم الخلط بين سياق this في الدوال العادية والثابتة: تذكر دائماً أن
thisفي الدالة الثابتة تشير للفئة نفسها وليس للكائن الفردي؛ تجنب محاولة قراءة الحقول غير الثابتة داخلها. - استخدام معدّلات الوصول مع الأعضاء الثابتة: تدعم TypeScript
public static، وprotected static، وprivate static. احرص على جعل المتغيرات الثابتة الحساسةprivateلحمايتها من التلاعب المباشر. - تفضيل الفئات المجردة أو دوال المصنع على التكرار: إذا كانت الفئة مخصصة فقط لتوفير دوال مساعدة وثوابت، اجعل المنشئ (Constructor) خاصاً أو اجعل الفئة
abstractلمنع استدعاءnewعليها بدون فائدة.
اختتام وخلاصة الدرس
وصلنا الآن إلى ختام رحلتنا المعمقة في عالم الخصائص والدوال الثابتة (Static Properties and Methods) ضمن دورة TypeScript من الصفر إلى الاحتراف (الدرس #15). لقد استكشفنا معًا هذا المفهوم الجوهري الذي يمنحك تحكماً فائقاً في هيكلة البيانات المشتركة وتجريد الوظائف الحسابية والهندسية المستقلة.
من خلال الجمع بين الخصائص الثابتة المحمية، ودوال المصانع، وأنماط التصميم مثل Singleton، تصبح قادراً على كتابة شيفرات برمجية عالية الأداء والنظافة تلبي متطلبات المشاريع الكبرى وتستغل كامل قدرات نظام الأنواع الصارم في TypeScript.
المراجع والمصادر الرسمية المعتمدة
- التوثيق الرسمي لـ TypeScript: الأعضاء الثابتة في الفئات (Static Members)
- شبكة مطوري موزيلا (MDN): الكلمة المفتاحية static في JavaScript الحديثة
- دورة TypeScript من الصفر إلى الاحتراف – منصة كود التطور
اكتشاف المزيد من كود التطور
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.
