اذهب إلى المحتوى

الخاصيات والتوابع الثابتة (Static properties and methods) في جافاسكربت


صفا الفليج

يمكننا أيضًا إسناد التوابِع إلى دالة الصنف ذاتها وليس إلى كائن "prototype" لها. نسمّي هذه التوابِع بالتوابِع ”الثابتة“ (static).

في الأصناف نضع بعدها كلمة static هكذا:

class User {
  static staticMethod() { // لاحظ
    alert(this === User);
  }
}

User.staticMethod(); // true

في الواقع، لا يفرق هذا عن إسنادها على أنّها خاصية بشيء:

class User() { }

User.staticMethod = function() {
  alert(this === User);
};

User.staticMethod(); // true

وتكون قيمة this في الاستدعاء User.staticMethod()‎ هي باني الصنف User ذاته (تذكّر قاعدة ”الكائن قبل النقطة“),

عادةً ما نستعمل التوابِع الثابتة لكتابة دوال تعود إلى الصنف نفسه وليس إلى أيّ كائن من ذلك الصنف.

مثال للتوضيح: لدينا كائنات مقالات Article ونريد دالة لموازنتها. الحل الطبيعي هو إضافة تابِع Article.compare هكذا:

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  // هنا
  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
}

// الاستعمال
let articles = [
  new Article("HTML", new Date(2019, 1, 1)),
  new Article("CSS", new Date(2019, 0, 1)),
  new Article("JavaScript", new Date(2019, 11, 1))
];

articles.sort(Article.compare); // لاحظ

alert( articles[0].title ); // CSS

نرى هنا التابِع Article.compare ”فوق“ المقالات فيتيح لنا طريقة لموازنتها. ليس التابِع تابِعًا للمقالة ذاتها، بل لصنف المقالات نفسه.

توابِع ”المصانع“ (factory) تنفعها أيضًا التوابِع الثابتة هذه. لنقل بأنّا نريد إنشاء المقالات بطرائق عدّة:

  1. بتمرير المُعاملات (العنوان title والتاريخ date وغيرها).
  2. إنشاء مقالة فارغة تحمل تاريخ اليوم.
  3. أو… كما تريد أنت.

يمكننا تنفيذ الطريقة الأولى باستعمال بانٍ، وللثانية صناعة تابِع ثابت للصنف.

مثل التابِع Article.createTodays()‎ هنا:

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    // لا تنسَ: this = Article
    return new this("Today's digest", new Date()); // موجز أحداث اليوم
  }
}

let article = Article.createTodays();

alert( article.title ); // موجز أحداث اليوم

الآن متى أردنا صناعة موجز عن أحداث اليوم، استدعينا Article.createTodays()‎. أُعيد، ليس هذا تابِعًا للمقالة نفسها بل تابِعًا للصنف كله.

كما نستعمل التوابِع الثابتة في أصناف قواعد البيانات للبحث عن المُدخلات وحفظها وإزالتها، هكذا:

// بفرض أنّ ‫ Article هو صنف مخصّص لإدارة المقالات
// نستعمل تابِعًا ثابتًا لإزالة المقالة:
Article.remove({id: 12345});

الخاصيات الثابتة

إضافة حديثة

انتبه رجاءً إلى أنه هذه الميزة مضافة حديثًا إلى اللغة، لذا لن تعمل الأمثلة إلى في الإصدارات الحديث من المتصفح كروم.

كما يمكننا أيضًا استعمال الخاصيات الثابتة. ظاهرها فهي خاصيات للأصناف، ولكن نضع قبلها عبارة static:

class Article {
  static publisher = "Ilya Kantor";
}

alert( Article.publisher ); // Ilya Kantor

لا يفرق هذا عن الإسناد المباشر إلى صنف Article:

Article.publisher = "Ilya Kantor";

وراثة الخاصيات والتوابِع الثابتة

هذه الأنواع من الخاصيات والتوابِع

فمثلًا التابِع Animal.compare والخاصية Animal.planet في الشيفرة أسفله موروثين بالأسماء Rabbit.compare و Rabbit.planet:

class Animal {
  static planet = "Earth"; // الأرض

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// نرث من الصنف Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare); // لاحظ

rabbits[0].run(); // Black Rabbit runs with speed 5.

alert(Rabbit.planet); // الأرض

الآن متى استدعينا Rabbit.compare، استدعى المحرّك التابِع Animal.compare الموروث.

ولكن كيف يعمل هذا الشيء؟ كالعادة، بكائنات prototype: تُقدّم extends للصنف Rabbit إشارة [[Prototype]] إلى الصنف Animal.

animal-rabbit-static.png

لذا فعبارة Rabbit extends Animal تصنع إشارتي [[Prototype]]:

  1. دالة Rabbit موروثة عبر prototype من دالة Animal.
  2. كائن Rabbit.prototype موروث عبر prototype من كائن Animal.prototype.

بهذا تعمل الوراثة للتوابِع العادية والثابتة معًا.

تشكّ؟ انظر للشيفرة:

class Animal {}
class Rabbit extends Animal {}

// لكلّ ما هو ثابت
alert(Rabbit.__proto__ === Animal); // true

// للتوابِع العادية
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true

خلاصة

نستعمل التوابِع الثابتة لأيّة وظائف تخصّ الصنف ”كلّه على بعضه“، ولا يخصّ سيرورة معيّنة من الصنف.

مثل تابع الموازنة Article.compare(article1, article2)‎ أو تابع المصنع Article.createTodays()‎.

ويصنفون كثوابت (static) في تعريف الصنف.

نستعمل الخاصيات الثابتة متى أردنا تخزين البيانات على مستوى الصنف لا على مستوى السيرورات.

صياغتها هي:

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}

تقنيًا فالتصريح الثابت (static declaration) لا يفرق عن الإسناد إلى الصنف مباشرةً:

MyClass.property = ...
MyClass.method = ...

الخاصيات والدوال الثابتة الموروثة.

من أجل العبارة class B extends A إن prototype للصنف B يشير إلى A:B.[[Prototype]] = A. لذا إن تعذر العثور على خاصية ما في الصنف B فسيستمر البحث في الصنف A.

ترجمة -وبتصرف- للفصل Static properties and methods من كتاب The JavaScript language


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...