١٥ ديسمبر ٢٠٢١

الصيغة الأساسية للClass

في البرمجة الموجهة للكائنات ، * الفئة * عبارة عن قالب رمز برنامج قابل للتوسيع لإنشاء الكائنات ، ويوفر القيم الأولية للحالة (متغيرات الأعضاء) وتنفيذ السلوك (وظائف الأعضاء أو الأساليب).

Wikipedia

من الناحية العملية ، نحتاج غالبًا إلى إنشاء العديد من العناصر من نفس النوع ، مثل المستخدمين أو السلع أو أيا كان.

كما نعلم بالفعل من الفصل <info: buildor-new> ، يمكن أن تساعد الوظيفة الجديدة في ذلك.

ولكن في جافا سكريبت الحديثة ، هناك بنية “صنف” أكثر تقدمًا ، تقدم ميزات جديدة رائعة مفيدة للبرمجة الموجهة للكائنات.

الصيغة الأساسية للClass

الصيغة الأساسية هي:

class MyClass {
  // class methods
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}

ثم استخدم New MyClass () لإنشاء كائن جديد بكل الطرق المدرجة.

يتم استدعاء طريقة constructor () تلقائيًا بواسطة new ، حتى نتمكن من تهيئة الكائن هناك.

فمثلا:

class User {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }
}

// Usage:
let user = new User('John');
user.sayHi();

When new User("John") is called:

  1. A new object is created.
  2. The constructor runs with the given argument and assigns it to this.name.

… ثم يمكننا استدعاء طرق الكائن ، مثل user.sayHi ().

لا فاصلة بين طرق الفصل

من المشاكل الشائعة للمطورين المبتدئين وضع فاصلة بين طرق الفصل ، مما يؤدي إلى خطأ في بناء الجملة.

لا يجب الخلط بين التدوين هنا وحرف الشيء. داخل الفصل الدراسي ، لا يلزم وجود فواصل.

ما هي الclass ؟

إذن ، ما هي “الطبقة” بالضبط؟ هذا ليس كيانًا جديدًا تمامًا على مستوى اللغة ، كما قد يعتقد المرء.

دعونا نكشف عن أي سحر ونرى ما هو الصف حقا. سيساعد ذلك في فهم العديد من الجوانب المعقدة.

في JavaScript ، الفئة هي نوع من الوظائف.

هنا ، ألق نظرة:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// proof: User is a function
alert(typeof User); // function

ما يفعله بناء مستخدم الفئة {... } حقًا هو:

  1. إنشاء وظيفة باسم “المستخدم” ، والتي تصبح نتيجة لإعلان الفئة. يتم أخذ رمز الوظيفة من طريقة المنشئ (يفترض أنها فارغة إذا لم نكتب مثل هذه الطريقة).
  2. يخزن طرق الفصل ، مثل sayHi ، فيUser.prototype.

بعد إنشاء كائن “مستخدم جديد” ، عندما نسمي أسلوبه ، يتم أخذه من النموذج الأولي ، تمامًا كما هو موضح في الفصل <info: function-prototype>. لذا فإن الكائن لديه حق الوصول إلى أساليب الفصل.

يمكننا توضيح نتيجة إعلان “مستخدم الفئة” كما يلي:

إليك كود لتتفكر فيه:

class User {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert(this.name);
  }
}

// class is a function
alert(typeof User); // function

// ...or, more precisely, the constructor method
alert(User === User.prototype.constructor); // true

// The methods are in User.prototype, e.g:
alert(User.prototype.sayHi); // alert(this.name);

// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

ليس مجرد سكر نحوي

أحيانًا يقول الناس أن “class” عبارة عن “سكر نحوي” (بنية تم تصميمها لتسهيل قراءة الأشياء ، ولكن لا تقدم أي شيء جديد) ، لأنه يمكننا في الواقع أن نعلن الشيء نفسه بدون كلمة “class” على الإطلاق:

// rewriting class User in pure functions

// 1. Create constructor function
function User(name) {
  this.name = name;
}
// a function prototype has "constructor" property by default,
// so we don't need to create it

// 2. Add the method to prototype
User.prototype.sayHi = function () {
  alert(this.name);
};

// Usage:
let user = new User('John');
user.sayHi();

نتيجة هذا التعريف هي نفسها. لذا ، هناك بالفعل أسباب تجعل من الممكن اعتبار “class” سكرًا نحويًا لتحديد المنشئ مع طرق النموذج الأولي.

لا تزال هناك اختلافات مهمة.

  1. أولاً ، يتم تصنيف دالة تم إنشاؤها بواسطة “class” بواسطة خاصية داخلية خاصة [[FunctionKind]]:" classConstructor ". لذلك فهي ليست تمامًا مثل إنشائها يدويًا.

تقوم اللغة بالتحقق من هذه الخاصية في أماكن متنوعة. على سبيل المثال ، على عكس الوظيفة العادية ، يجب أن يتم استدعاؤها بـ new:

```js run
class User {
  constructor() {}
}

alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
```

أيضًا ، يبدأ تمثيل السلسلة لمنشئ فئة في معظم محركات JavaScript بـ “class …”

```js run
class User {
  constructor() {}
}

alert(User); // class User { ... }
```

هناك اختلافات أخرى ، سنراها قريبًا.

  1. دوال الclass لا تعد ولا تحصى.      يقوم تعريف الفئة بتعيين علامة enumerable إلىfalse لجميع الطرق في “” prototype "`.

هذا أمر جيد ، لأنه إذا كنا 'for…in` فوق كائن ما ، فإننا عادة لا نريد طرقه الطبقية.

  1. الclasses دائمًا “استخدام صارم”.      تكون جميع التعليمات البرمجية داخل بنية الفصل تلقائيًا في وضع صارم.

بالإضافة إلى ذلك ، فإن بناء جملة class يجلب العديد من الميزات الأخرى التي سنستكشفها لاحقًا.

تعبير الclass

تمامًا مثل functions ، يمكن تعريف الفئات داخل تعبير آخر ، وتمريرها ، وإعادتها ، وتعيينها ، وما إلى ذلك.

إليك مثالاً على تعبير class:

let User = class {
  sayHi() {
    alert('Hello');
  }
};

على غرار تعبيرات الوظائف المسماة ، قد يكون لتعبيرات الفئة اسم.

إذا كان تعبير فئة له اسم ، فإنه يكون مرئيًا داخل الفصل فقط:

// "Named Class Expression"
// (no such term in the spec, but that's similar to Named Function Expression)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass name is visible only inside the class
  }
};

new User().sayHi(); // works, shows MyClass definition

alert(MyClass); // error, MyClass name isn't visible outside of the class

يمكننا أيضًا أن نجعل classes ديناميكيًا “حسب الطلب” ، مثل هذا:

function makeClass(phrase) {
  // declare a class and return it
  return class {
    sayHi() {
      alert(phrase);
    }
  };
}

// Create a new class
let User = makeClass('Hello');

new User().sayHi(); // Hello

Getters/setters

تمامًا مثل الأشياء الحرفية ، قد تتضمن الفئات الحروف / المستوطنين والخصائص المحسوبة وما إلى ذلك.

في ما يلي مثال لـ user.name تم تنفيذه باستخدامget / set:

class User {

  constructor(name) {
    // invokes the setter
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short.");
      return;
    }
    this._name = value;
  }

}

let user = new User("John");
alert(user.name); // John

user = new User(""); // Name is too short.

من الناحية الفنية ، يعمل إعلان الفئة هذا عن طريق إنشاء الحروف والمستوطنين في User.prototype.

الأسماء المحسوبة […]

في ما يلي مثال باسم الطريقة المحسوبة باستخدام الأقواس [...]:

class User {

  ['say' + 'Hi']() {
    alert("Hello");
  }

}

new User().sayHi();

من السهل تذكر هذه الميزات ، لأنها تشبه تلك الموجودة في الأشياء الحرفية.

حقول class

قد تحتاج المتصفحات القديمة إلى ملف متعدد

تعد حقول الصف إضافة حديثة للغة.

في السابق ، كانت فصولنا تمتلك طرقًا فقط.

“حقول class” هي بنية تسمح بإضافة أي خصائص.

على سبيل المثال ، دعنا نضيف خاصية name إلىclass User:

class User {
  name = "John";

  sayHi() {
    alert(`Hello, ${this.name}!`);
  }
}

new User().sayHi(); // Hello, John!

لذلك ، نكتب فقط “<اسم الخاصية> = <قيمة>” في الإعلان ، وهذا كل شيء.

الاختلاف المهم في حقول الصف هو أنه يتم تعيينها على كائنات فردية ، وليس User.prototype:

class User {
  name = "John";
}

let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined

من الناحية الفنية ، تتم معالجتها بعد أن يقوم المنشئ بعمله ، ويمكننا استخدامه بالنسبة لهم التعبيرات المعقدة واستدعاءات الوظائف:

class User {
  name = prompt("Name, please?", "John");
}

let user = new User();
alert(user.name); // John

عمل طرق مرتبطة بحقول class

كما هو موضح في الفصل <info: bind> ، فإن وظائف JavaScript لها ديناميكية this. يعتمد ذلك على سياق المكالمة.

لذلك إذا تم تمرير طريقة كائن واستدعاؤها في سياق آخر ، فلن يكون هذا مرجعاً إلى كائنه بعد الآن.

على سبيل المثال ، سيُظهر هذا الرمز undefined:

class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // undefined

تسمى المشكلة “فقدان” this “”.

هناك طريقتان لإصلاحها ، كما هو موضح في الفصل <info: bind>:

  1. قم بتمرير دالة مجمعة ، مثل setTimeout (() => button.click ()، 1000).
  2. ربط طريقة الاعتراض ، على سبيل المثال في المنشئ:

توفر حقول class بنية أكثر أناقة للحل الأخير:

class Button {
  constructor(value) {
    this.value = value;
  }
  click = () => {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // hello

ينشئ حقل الفئة click = () => {...} وظيفة مستقلة على كل كائن Button ، معthis مرتبطًا بالكائن. ثم يمكننا تمرير “button.click” في أي مكان ، وسيتم استدعاؤها باستخدام this الصحيح.

هذا مفيد بشكل خاص في بيئة المتصفح ، عندما نحتاج إلى إعداد طريقة كمستمع للأحداث.

ملخص

تبدو بنية الصف الأساسية كما يلي:

class MyClass {
  prop = value; // property

  constructor(...) { // constructor
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter method
  set something(...) {} // setter method

  [Symbol.iterator]() {} // method with computed name (symbol here)
  // ...
}

MyClass هي وظيفة من الناحية الفنية (تلك التي نقدمها على أنها مُنشئ) ، بينما تتم كتابة الأساليب والرسومات والمستوطنين علىMyClass.prototype.

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

مهمه

The Clock class (see the sandbox) is written in functional style. Rewrite it in the “class” syntax.

ملاحظة. تدق الساعة في وحدة التحكم ، افتحها لترى.

افتح sandbox للمهمه.

class Clock {
  constructor({ template }) {
    this.template = template;
  }

  render() {
    let date = new Date();

    let hours = date.getHours();
    if (hours < 10) hours = '0' + hours;

    let mins = date.getMinutes();
    if (mins < 10) mins = '0' + mins;

    let secs = date.getSeconds();
    if (secs < 10) secs = '0' + secs;

    let output = this.template
      .replace('h', hours)
      .replace('m', mins)
      .replace('s', secs);

    console.log(output);
  }

  stop() {
    clearInterval(this.timer);
  }

  start() {
    this.render();
    this.timer = setInterval(() => this.render(), 1000);
  }
}


let clock = new Clock({template: 'h:m:s'});
clock.start();

افتح الحل في sandbox.

خريطة الدورة التعليمية