جافا سكريبت وجوه المنحى بعبارات بسيطة





يوم جيد ، أيها الأصدقاء!



هناك 4 طرق في JavaScript لإنشاء كائن:



  • وظيفة المنشئ
  • فئة (فئة)
  • ارتباط الكائن بكائن آخر (OLOO)
  • وظيفة المصنع


ما الطريقة التي يجب أن تستخدمها؟ أي واحد هو الأفضل؟



للإجابة على هذه الأسئلة ، لن نأخذ في الاعتبار كل نهج على حدة فحسب ، بل سنقارن أيضًا الفئات ووظائف المصنع وفقًا للمعايير التالية: الوراثة ، والتغليف ، والكلمة الأساسية "هذا" ، ومعالجات الأحداث.



لنبدأ بماهية البرمجة الشيئية (OOP).



ما هو OOP؟



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



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



الجانب الثاني من OOP هو هيكلة الكود عندما يكون لدينا عدة مشاريع بمستويات مختلفة. وهذا ما يسمى بالوراثة أو التصنيف الفرعي.



الجانب الثالث من OOP هو التغليف ، عندما نخفي تفاصيل التنفيذ عن الغرباء ، مما يجعل المتغيرات والوظائف غير قابلة للوصول من الخارج. هذا هو جوهر أنماط تصميم الوحدة والواجهة.



دعنا ننتقل إلى طرق إنشاء الكائنات.



طرق إنشاء الكائن



وظيفة المنشئ


المُنشئون هم وظائف تستخدم الكلمة الأساسية "this".



    function Human(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }


هذا يسمح لك بتخزين والوصول إلى القيم الفريدة للمثيل الذي يتم إنشاؤه. يتم إنشاء المثيلات باستخدام الكلمة الأساسية "الجديدة".



const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew


صف دراسي


الفئات هي عبارة عن تجريد ("سكر نحوي") على وظائف المُنشئ. أنها تجعل من السهل إنشاء حالات.



    class Human {
        constructor(firstName, lastName) {
            this.firstName = firstName
            this.lastName = lastName
        }
    }


لاحظ أن المُنشئ يحتوي على نفس الكود مثل دالة المُنشئ أعلاه. يتعين علينا القيام بذلك من أجل تهيئة هذا. يمكننا حذف دالة الإنشاء إذا لم نكن بحاجة إلى تعيين قيم أولية.



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



يتم أيضًا إنشاء المثيلات باستخدام الكلمة الأساسية "الجديدة".



const chris = new Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


ربط الأشياء


تم اقتراح طريقة إنشاء الكائنات هذه من قبل كايل سيمبسون. في هذا النهج ، نحدد المشروع ككائن عادي. بعد ذلك ، باستخدام طريقة (تسمى عادةً init ، ولكن هذا ليس مطلوبًا ، على عكس المُنشئ في الفئة) ، نقوم بتهيئة المثيل.



const Human = {
    init(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }
}


يتم استخدام Object.create لإنشاء مثيل. بعد إنشاء مثيل ، يتم استدعاء الحرف الأول.



const chris = Object.create(Human)
chris.init('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


يمكن تحسين الكود قليلاً عن طريق إعادة هذا إلى init.



const Human = {
  init () {
    // ...
    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


وظيفة المصنع


وظيفة المصنع هي وظيفة تقوم بإرجاع كائن. يمكن إرجاع أي كائن. يمكنك حتى إرجاع مثيل لفئة أو ارتباطات كائن.



هذا مثال بسيط لوظيفة المصنع.



function Human(firstName, lastName) {
    return {
        firstName,
        lastName
    }
}


لا نحتاج إلى الكلمة الأساسية "هذه" لإنشاء مثيل. نحن فقط نسمي الوظيفة.



const chris = Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


الآن دعونا نلقي نظرة على طرق إضافة الخصائص والطرق.



تحديد الخصائص والطرق



الأساليب هي وظائف تم إعلانها كخصائص كائن.



    const someObject = {
        someMethod () { /* ... */ }
    }


في OOP ، هناك طريقتان لتحديد الخصائص والطرق:



  • على سبيل المثال
  • في النموذج الأولي


تحديد الخصائص والطرق في المنشئ


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



function Human (firstName, lastName) {
  //  
  this.firstName = firstName
  this.lastname = lastName

  //  
  this.sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}

const chris = new Human('Chris', 'Coyier')
console.log(chris)






عادةً ما يتم تحديد الطرق في النموذج الأولي ، حيث يتجنب ذلك إنشاء دالة لكل حالة ، أي يسمح لجميع المثيلات بمشاركة وظيفة واحدة (تسمى وظيفة مشتركة أو موزعة).



لإضافة خاصية إلى النموذج الأولي ، استخدم النموذج الأولي.



function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}

//    
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}






يمكن أن يكون إنشاء طرق متعددة مملاً.



//    
Human.prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }


يمكنك جعل حياتك أسهل مع تعيين الكائنات.



Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})


تحديد الخصائص والطرق في الفصل


يمكن تعريف خصائص المثيل في المُنشئ.



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
      this.lastname = lastName

      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}






يتم تعريف خصائص النموذج الأولي بعد المُنشئ كدالة عادية.



class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}






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



class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  method1 () { /*...*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}


تحديد الخصائص والطرق عند ربط الكائنات


لتحديد خصائص لمثيل ، نضيف خاصية إلى هذا.



const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }

    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris)






يتم تعريف طريقة النموذج الأولي ككائن عادي.



const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}






تحديد الخصائص والطرق في وظائف المصنع (FF)


يمكن تضمين الخصائص والأساليب في الكائن المرتجع.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}






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



//   
function createHuman (...args) {
  return new Human(...args)
}


أين يتم تحديد الخصائص والطرق



أين يجب أن تحدد الخصائص والطرق؟ مثال أو نموذج أولي؟



يعتقد الكثير من الناس أن النماذج الأولية أفضل لهذا الغرض.



ومع ذلك ، لا يهم حقًا.



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



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



تقريبا. لكل منهما: اسمحوا لي أن أختلف مع المؤلف. إن مسألة استخدام النماذج الأولية بدلاً من الحالات عند تعريف الخصائص والأساليب ليست فقط مسألة استهلاك للذاكرة ، ولكن قبل كل شيء مسألة تتعلق بالغرض من الخاصية أو الطريقة التي يتم تحديدها. إذا كان يجب أن تكون خاصية أو طريقة فريدة لكل مثيل ، فيجب تحديدها في المثيل. إذا كانت الخاصية أو الطريقة هي نفسها (شائعة) لجميع المثيلات ، فيجب تحديدها في النموذج الأولي. في الحالة الأخيرة ، إذا كنت بحاجة إلى إجراء تغييرات على خاصية أو طريقة ، فسيكون ذلك كافيًا لإدخالها على النموذج الأولي ، على عكس خصائص وطرق المثيلات ، والتي يتم تعديلها بشكل فردي.



الاستنتاج الأولي



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



  • تعتبر الفئات أفضل من المنشئين لأنها تسهل تحديد طرق متعددة.
  • يبدو ربط الكائن غريبًا بسبب الحاجة إلى استخدام Object.create. ظللت أنسى هذا عند دراسة هذا النهج. بالنسبة لي ، كان هذا سببًا كافيًا لرفض المزيد من الاستخدام.
  • تعتبر الفئات و FFs هي الأسهل في الاستخدام. المشكلة هي أنه لا يمكن استخدام النماذج الأولية في FF. لكن كما أشرت سابقًا ، لا يهم حقًا.


بعد ذلك ، سنقارن الفئات و FFs كأفضل طريقتين لإنشاء كائنات في JavaScript.



الفئات مقابل FF - الميراث



قبل الانتقال إلى مقارنة الفئات و FFs ، تحتاج إلى التعرف على المفاهيم الثلاثة الكامنة وراء OOP:



  • ميراث
  • التغليف
  • هذه


لنبدأ بالميراث.



ما هو الميراث؟


في JavaScript ، تعني الوراثة تمرير الخصائص من الأصل إلى الطفل ، أي من مشروع إلى آخر.



هذا يحدث بطريقتين:



  • باستخدام تهيئة المثيل
  • باستخدام سلسلة النموذج الأولي


في الحالة الثانية ، يتم توسيع المشروع الأصلي بمشروع فرعي. وهذا ما يسمى بالفئة الفرعية ، لكن البعض يسميها أيضًا بالوراثة.



فهم التصنيف الفرعي


يحدث التصنيف الفرعي عندما يقوم مشروع فرعي بتوسيع المشروع الأصل.



دعونا نلقي نظرة على مثال الطبقات.



تصنيف فرعي مع فئة


يتم استخدام الكلمة الأساسية "يمتد" لتوسيع فئة الأصل.



class Child extends Parent {
    // ...
}


على سبيل المثال ، دعنا ننشئ فئة "Developer" التي توسع فئة "Human".



//  Human
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}


ستمتد فئة المطور Human على النحو التالي:



class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }

    // ...
}


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



class Developer extends Human {
  // ...
}


لنفترض أن المطور يمكنه كتابة كود (من كان يظن). دعنا نضيف طريقة مقابلة لها.



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


فيما يلي مثال على مثيل لفئة "Developer".



const chris = new Developer('Chris', 'Coyier')
console.log(chris)






تصنيف فرعي مع FF


لإنشاء فئات فرعية باستخدام FF ، تحتاج إلى تنفيذ 4 خطوات:



  • إنشاء FF جديد
  • إنشاء مثيل للمشروع الأصلي
  • إنشاء نسخة من هذا المثال
  • أضف خصائص وأساليب لهذه النسخة


هذه العملية تبدو هكذا.



function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    //   
  })
}


لنقم بإنشاء فئة فرعية "مطور". هذا ما يبدو عليه "الإنسان" FF.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}


إنشاء مطور.



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    //   
  })
}


أضف طريقة "التعليمات البرمجية" إليها.



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}


قم بإنشاء مثيل للمطور.



const chris = Developer('Chris', 'Coyier')
console.log(chris)






الكتابة فوق طريقة الأصل


في بعض الأحيان يصبح من الضروري الكتابة فوق طريقة أصل داخل فئة فرعية. ويمكن القيام بذلك على النحو التالي:



  • إنشاء طريقة بنفس الاسم
  • استدعاء طريقة الوالدين (اختياري)
  • إنشاء طريقة جديدة في الفئة الفرعية


هذه العملية تبدو هكذا.



class Developer extends Human {
  sayHello () {
    //   
    super.sayHello()

    //   
    console.log(`I'm a developer.`)
  }
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()






نفس العملية باستخدام FF.



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)

  return Object.assign({}, human, {
      sayHello () {
        //   
        human.sayHello()

        //   
        console.log(`I'm a developer.`)
      }
  })
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()






الميراث مقابل التكوين


نادراً ما يتم الحديث عن الميراث دون ذكر التكوين. يعتقد الخبراء مثل إريك إليوت أنه يجب استخدام التكوين كلما أمكن ذلك.



ما هو التكوين؟



فهم التكوين


في الأساس ، التكوين هو مزيج من عدة أشياء في واحد. الطريقة الأكثر شيوعًا والأبسط لدمج الكائنات هي استخدام Object.assign.



const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)


من السهل شرح التركيب بمثال. لنفترض أن لدينا فئتين فرعيتين ، المطور والمصمم. يعرف المصممون كيفية التصميم ، ويعرف المطورون كيفية كتابة التعليمات البرمجية. كلاهما يرث من فئة "الإنسان".



class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}

class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


الآن ، لنفترض أننا نريد إنشاء فئة فرعية ثالثة. يجب أن تكون هذه الفئة الفرعية مزيجًا من المصمم والمطور - يجب أن تكون قادرة على تصميم وكتابة التعليمات البرمجية. دعنا نسميها DesignerDeveloper (أو DeveloperDesigner ، إذا كنت تفضل ذلك).



كيف نصنعها؟



لا يمكننا تمديد فئتي "المصمم" و "المطور" في نفس الوقت. هذا غير ممكن لأننا لا نستطيع تحديد الخصائص التي يجب أن تأتي أولاً. وهذا ما يسمى مشكلة الماس (وراثة الماس) .







يمكن حل مشكلة المعين باستخدام Object. assign إذا أعطينا كائنًا أولوية على آخر. ومع ذلك ، فإن JavaScript لا يدعم الوراثة المتعددة.



//  
class DesignerDeveloper extends Developer, Designer {
  // ...
}


هذا هو المكان الذي يكون فيه التكوين مفيدًا.



يوضح هذا الأسلوب ما يلي: بدلاً من التصنيف الفرعي DesignerDeveloper ، قم بإنشاء كائن يحتوي على المهارات التي يمكنك تصنيفها حسب الحاجة.



يؤدي تنفيذ هذا النهج إلى ما يلي.



const skills = {
    code (thing) { /* ... */ },
    design (thing) { /* ... */ },
    sayHello () { /* ... */ }
}


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



هذا هو كود DesignerDeveloper.



class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)






يمكننا أن نفعل الشيء نفسه للمصمم والمطور.



class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    })
  }
}


هل لاحظت أننا أنشأنا طرقًا على مثيل؟ هذا مجرد واحد من الخيارات الممكنة. يمكننا أيضًا وضع طرق في النموذج الأولي ، لكنني أجدها غير ضرورية (يبدو أن هذا النهج قد عدنا إلى المنشئين).



class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})






استخدم النهج الذي يناسبك. وستكون النتيجة نفسها.



تكوين مع FF


التركيب باستخدام FF هو حول إضافة طرق موزعة إلى الكائن المرتجع.



function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}






الميراث والتكوين


لم يقل أحد أنه لا يمكننا استخدام الميراث والتكوين في نفس الوقت.



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



هنا مثال على الوراثة والتكوين باستخدام بناء الجملة.



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})






وهنا نفس الشيء مع استخدام FF.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  })
}






الفئات الفرعية في العالم الحقيقي


بينما يجادل العديد من الخبراء بأن التركيب أكثر مرونة (وبالتالي أكثر فائدة) من الفئات الفرعية ، فلا ينبغي استبعاد الفئات الفرعية. تستند العديد من الأشياء التي نتعامل معها على هذه الاستراتيجية.



على سبيل المثال: حدث "النقر" هو حدث MouseEvent. MouseEvent هي فئة فرعية من UIEvent (حدث واجهة المستخدم) ، والتي تعد بدورها فئة فرعية من الأحداث (الحدث).







مثال آخر: عناصر HTML هي فئات فرعية من العقد. لذلك ، يمكنهم استخدام جميع خصائص وطرق العقد.







الاستنتاج الأولي بشأن الميراث


يمكن استخدام الوراثة والتكوين في كلا الفئتين و FF. في FF ، تبدو التركيبة "أنظف" ، لكن هذه ميزة طفيفة على الفئات.



دعنا نواصل المقارنة.



الفئات مقابل FF - التغليف



في الأساس ، يتعلق التغليف بإخفاء شيء داخل شيء آخر ، مما يجعل الجوهر الداخلي غير ممكن الوصول إليه من الخارج.



في JavaScript ، الكيانات المخفية عبارة عن متغيرات ووظائف متاحة فقط في السياق الحالي. في هذه الحالة ، السياق هو نفسه النطاق.



تغليف بسيط


أبسط شكل من أشكال التغليف هو كتلة من التعليمات البرمجية.



{
  // ,  ,     
}


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



const food = 'Hamburger'

{
  console.log(food)
}






لكن ليس العكس.



{
  const food = 'Hamburger'
}

console.log(food)






لاحظ أن المتغيرات المُعلنة بالكلمة الأساسية "var" لها نطاق عام أو وظيفي. حاول ألا تستخدم var للإعلان عن المتغيرات.



تغليف مع وظيفة


النطاق الوظيفي مشابه لنطاق الكتلة. يمكن الوصول إلى المتغيرات المُعلنة في دالة داخلها فقط. هذا ينطبق على جميع المتغيرات ، حتى تلك المعلنة مع var.



function sayFood () {
  const food = 'Hamburger'
}

sayFood()
console.log(food)






عندما نكون داخل دالة ، يمكننا الوصول إلى المتغيرات المعلنة خارجها.



const food = 'Hamburger'

function sayFood () {
  console.log(food)
}

sayFood()






يمكن أن ترجع الدالات القيم التي يمكن استخدامها لاحقًا خارج الوظيفة.



function sayFood () {
  return 'Hamburger'
}

console.log(sayFood())






إغلاق


الإغلاق هو شكل متقدم من أشكال التغليف. إنها مجرد وظيفة داخل دالة أخرى.



//  
function outsideFunction () {
  function insideFunction () { /* ... */ }
}




المتغيرات المعلنة في الخارجيمكن استخدام الدالة في الداخل.



function outsideFunction () {
  const food = 'Hamburger'
  console.log('Called outside')

  return function insideFunction () {
    console.log('Called inside')
    console.log(food)
  }
}

//  outsideFunction,   insideFunction
//  insideFunction   "fn"
const fn = outsideFunction()






التغليف و OOP


عند إنشاء كائنات ، نريد أن تكون بعض الخصائص عامة (عامة) وأخرى خاصة (خاصة أو خاصة).



لنلقي نظرة على مثال. لنفترض أن لدينا مشروع سيارة. عند إنشاء مثيل جديد ، نضيف إليه خاصية "وقود" بقيمة 50.



class Car {
  constructor () {
    this.fuel = 50
  }
}




يمكن للمستخدمين استخدام هذه الخاصية لتحديد كمية الوقود المتبقية.



const car = new Car()
console.log(car.fuel) // 50




يمكن للمستخدمين أيضًا تحديد كمية الوقود بأنفسهم.



const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000


دعنا نضيف شرط أن خزان السيارة يحتوي على 100 لتر من الوقود كحد أقصى. لا نريد أن يكون المستخدمون قادرين على تحديد كمية الوقود بأنفسهم ، لأنهم يستطيعون كسر السيارة.



هناك طريقتان للقيام بذلك:



  • استخدام الممتلكات الخاصة عن طريق الاتفاقية
  • باستخدام الحقول الخاصة الحقيقية


الممتلكات الخاصة بالاتفاق


في JavaScript ، عادةً ما يتم الإشارة إلى المتغيرات والخصائص الخاصة بشرطة سفلية.



class Car {
  constructor () {
    //   "fuel"  ,       
    this._fuel = 50
  }
}


عادة ، نقوم بإنشاء طرق لإدارة الممتلكات الخاصة.



class Car {
  constructor () {
    this._fuel = 50
  }

  getFuel () {
    return this._fuel
  }

  setFuel (value) {
    this._fuel = value
    //   
    if (value > 100) this._fuel = 100
  }
}


يجب على المستخدمين استخدام أساليب getFuel و setFuel لتحديد كمية الوقود وتعيينها ، على التوالي.



const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100


لكن المتغير "_fuel" ليس خاصًا حقًا. يمكن الوصول إليه من الخارج.



const car = new Car()
console.log(car.getFuel()) // 50

car._fuel = 3000
console.log(car.getFuel()) // 3000


استخدم الحقول الخاصة الحقيقية لتقييد الوصول إلى المتغيرات.



حقا المجالات الخاصة


الحقول هو المصطلح المستخدم لدمج المتغيرات والخصائص والطرق.



حقول الفصول الخاصة


تسمح لك الفئات بإنشاء متغيرات خاصة باستخدام البادئة "#".



class Car {
  constructor () {
    this.#fuel = 50
  }
}


لسوء الحظ ، لا يمكن استخدام هذه البادئة في المنشئ.







يجب تحديد المتغيرات الخاصة خارج المنشئ.



class Car {
  //   
  #fuel
  constructor () {
    //  
    this.#fuel = 50
  }
}


في هذه الحالة ، يمكننا تهيئة المتغير عند تعريفه.



class Car {
  #fuel = 50
}


الآن المتغير "#fuel" متاح فقط داخل الفصل. ستؤدي محاولة الوصول إليه خارج الفصل الدراسي إلى حدوث خطأ.



const car = new Car()
console.log(car.#fuel)






نحتاج إلى طرق مناسبة للتعامل مع المتغير.



class Car {
  #fuel = 50

  getFuel () {
    return this.#fuel
  }

  setFuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100


أنا شخصياً أفضل استخدام أدوات الإلمام والمحددات لهذا الغرض. أجد أن بناء الجملة هذا أكثر قابلية للقراءة.



class Car {
  #fuel = 50

  get fuel () {
    return this.#fuel
  }

  set fuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100


حقول FF الخاصة


تقوم FFs بإنشاء حقول خاصة تلقائيًا. نحتاج فقط إلى إعلان متغير. لن يتمكن المستخدمون من الوصول إلى هذا المتغير من الخارج. هذا يرجع إلى حقيقة أن المتغيرات لها نطاق كتلة (أو وظيفي) ، أي بشكل افتراضي.



function Car () {
  const fuel = 50
}

const car = new Car()
console.log(car.fuel) // undefined
console.log(fuel) // Error: "fuel" is not defined


كما تُستخدم أدوات التحصيل والضبط للتحكم في "الوقود" المتغير الخاص.



function Car () {
  const fuel = 50

  return {
    get fuel () {
      return fuel
    },

    set fuel (value) {
      fuel = value
      if (value > 100) fuel = 100
    }
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100


مثله. بكل بساطة وسهولة!



الاستنتاج الأولي بخصوص التغليف


تغليف FF أبسط وأسهل في الفهم. يعتمد على النطاق ، وهو جزء مهم من JavaScript.



يتضمن تغليف الفصل استخدام البادئة "#" ، والتي يمكن أن تكون مملة إلى حد ما.



دروس ضد FF - هذا



هذه هي الحجة الرئيسية ضد استخدام الفئات. لماذا ا؟ لأن معنى هذا يعتمد على مكان وكيفية استخدام هذا. غالبًا ما يكون هذا السلوك محيرًا ليس فقط للمبتدئين ، ولكن أيضًا للمطورين ذوي الخبرة.



ومع ذلك ، فإن مفهوم هذا في الواقع ليس بهذه الصعوبة. هناك 6 سياقات في المجموع يمكن استخدام هذا فيها. إذا فهمت هذه السياقات ، فلن تواجه أي مشاكل في ذلك.



السياقات المسماة هي:



  • السياق العالمي
  • سياق الكائن الذي يتم إنشاؤه
  • سياق خاصية أو طريقة كائن
  • وظيفة بسيطة
  • وظيفة السهم
  • سياق معالج الحدث


لكن العودة إلى المقال. دعونا نلقي نظرة على تفاصيل استخدام هذا في الفصول الدراسية و FFs.



استخدام هذا في الفصول الدراسية


عند استخدامه في فئة ، يشير هذا إلى المثيل الذي يتم إنشاؤه (سياق الخاصية / الطريقة). هذا هو سبب تهيئة المثيل في المُنشئ.



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    console.log(this)
  }
}

const chris = new Human('Chris', 'Coyier')






استخدام هذا في دوال الباني


عند استخدام هذا داخل دالة وجديدة لإنشاء مثيل ، سيشير هذا إلى المثيل.



function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
  console.log(this)
}

const chris = new Human('Chris', 'Coyier')






على عكس FK في FF ، يشير هذا إلى نافذة (في سياق الوحدة النمطية ، يكون لهذا بشكل عام القيمة "غير محدد").



//        "new"
function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
  console.log(this)
}

const chris = Human('Chris', 'Coyier')






لذلك ، لا ينبغي استخدام هذا في FF. هذا هو أحد الاختلافات الرئيسية بين FF و FC.



باستخدام هذا في FF


لتتمكن من استخدام هذا في FF ، من الضروري إنشاء سياق خاصية / طريقة.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayThis () {
      console.log(this)
    }
  }
}

const chris = Human('Chris', 'Coyier')
chris.sayThis()






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



function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${human.firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()


يعد human.firstName أكثر دقة من this.firstName لأن الإنسان يشير صراحة إلى مثيل.



في الواقع ، لا نحتاج حتى إلى كتابة human.firstName. يمكننا أن نحصر أنفسنا في الاسم الأول ، لأن هذا المتغير له نطاق معجمي (هذا عندما يتم أخذ قيمة المتغير من البيئة الخارجية).



function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()






لنلق نظرة على مثال أكثر تعقيدًا.



مثال معقد



الشروط كالتالي: لدينا مشروع "Human" بخصائص "firstName" و "lastName" وطريقة "sayHello".



لدينا أيضًا مشروع "مطور" يرث من الإنسان. يعرف المطورون كيفية كتابة التعليمات البرمجية ، لذلك يجب أن يكون لديهم طريقة "رمز". بالإضافة إلى ذلك ، يجب أن يعلنوا أنهم في طبقة المطور ، لذلك نحتاج إلى الكتابة فوق طريقة sayHello.



دعنا ننفذ المنطق المحدد باستخدام الفئات و FF.



الطبقات


نقوم بإنشاء مشروع "بشري".



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}


إنشاء مشروع "مطور" باستخدام أسلوب "التعليمات البرمجية".



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


نقوم بالكتابة فوق طريقة "sayHello".



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }

  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}


FF (باستخدام هذا)


نقوم بإنشاء مشروع "بشري".



function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}


إنشاء مشروع "مطور" باستخدام أسلوب "التعليمات البرمجية".



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}


نقوم بالكتابة فوق طريقة "sayHello".



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I\'m a developer')
    }
  })
}


وما يليها (بدون هذا)


نظرًا لأن firstName محددًا بشكل معجمي مباشرة ، يمكننا حذف هذا.



function Human (firstName, lastName) {
  return {
    // ...
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  // ...
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${firstName} coded ${thing}`)
    },

    sayHello () { /* ... */ }
  })
}


استنتاج أولي بخصوص هذا


بكلمات بسيطة ، تتطلب الفئات استخدام هذا ، لكن FFs لا تتطلب ذلك. في هذه الحالة ، أفضل استخدام FF للأسباب التالية:



  • هذا السياق يمكن أن يتغير
  • الكود المكتوب باستخدام FF أقصر وأنظف (أيضًا بسبب التغليف التلقائي للمتغيرات)


الفئات مقابل FF - معالجات الأحداث



تتجاهل العديد من المقالات حول OOP حقيقة أننا كمطورين للواجهة الأمامية نتعامل باستمرار مع معالجي الأحداث. أنها توفر التفاعل مع المستخدمين.



نظرًا لأن معالجي الأحداث يغيرون هذا السياق ، فإن العمل معهم في الفصول الدراسية يمكن أن يكون مشكلة. في الوقت نفسه ، لا تنشأ مثل هذه المشاكل في FF.



ومع ذلك ، فإن تغيير هذا السياق لا يهم إذا عرفنا كيفية التعامل معه. لنلق نظرة على مثال بسيط.



إنشاء العداد


لإنشاء عداد ، سنستخدم المعرفة المكتسبة ، بما في ذلك المتغيرات الخاصة.



سيحتوي عدادنا على شيئين:



  • العداد نفسه
  • زر لزيادة قيمتها






هذا ما قد يبدو عليه الترميز:



<div class="counter">
  <p>Count: <span>0</span></p>
  <button>Increase Count</button>
</div>


إنشاء عداد باستخدام فئة


لتسهيل الأمور ، اطلب من المستخدم البحث عن علامة العداد وتمريرها إلى فئة العداد:



class Counter {
  constructor (counter) {
    // ...
  }
}

// 
const counter = new Counter(document.querySelector('.counter'))


تحتاج إلى الحصول على عنصرين في الفصل:



  • <span> تحتوي على قيمة العداد - نحتاج إلى تحديث هذه القيمة عندما يزيد العداد
  • <button> - نحتاج إلى إضافة معالج حدث لهذا العنصر


class Counter {
  constructor (counter) {
    this.countElement = counter.querySelector('span')
    this.buttonElement = counter.querySelector('button')
  }
}


بعد ذلك ، نقوم بتهيئة المتغير "count" بالمحتوى النصي لـ countElement. يجب أن يكون المتغير المحدد خاصًا.



class Counter {
  #count
  constructor (counter) {
    // ...

    this.#count = parseInt(countElement.textContent)
  }
}


عند الضغط على الزر ، يجب زيادة قيمة العداد بمقدار 1. نقوم بتنفيذ ذلك باستخدام طريقة "زيادة العد".



class Counter {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
  }
}


الآن نحن بحاجة إلى تحديث DOM. دعنا ننفذ هذا باستخدام طريقة "updateCount" التي تسمى Inside "زيادة":



class Counter {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
    this.updateCount()
  }

  updateCount () {
    this.countElement.textContent = this.#count
  }
}


يبقى لإضافة معالج الحدث.



إضافة معالج الحدث


دعونا نضيف معالج إلى هذا الزر عنصر. لسوء الحظ ، لا يمكننا استخدام زيدكونت كدالة رد. سيؤدي هذا إلى حدوث خطأ.



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  // 
}






تم طرح الاستثناء لأن هذا يشير إلى buttonElement (سياق معالج الحدث). يمكنك التحقق من ذلك عن طريق طباعة هذه القيمة على وحدة التحكم.







يجب تغيير هذه القيمة للإشارة إلى المثيل. ويمكن أن يتم ذلك بطريقتين:



  • باستخدام الربط
  • باستخدام وظيفة السهم


يستخدم معظمهم الطريقة الأولى (لكن الثانية أبسط).



إضافة معالج حدث مع ربط


ربط إرجاع وظيفة جديدة. كأول وسيطة ، يتم تمريرها إلى كائن يشير إليه هذا (والذي سيكون مرتبطًا به).



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount.bind(this))
  }

  // ...
}


إنه يعمل ، لكنه لا يبدو جيدًا. بالإضافة إلى ذلك ، يعد الربط ميزة متقدمة يصعب على المبتدئين التعامل معها.



دالات السهم


وظائف السهم ، من بين أمور أخرى ، ليس لها هذا الخاص بها. يقترضونها من البيئة المعجمية (الخارجية). لذلك ، يمكن إعادة كتابة رمز العداد على النحو التالي:



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', () => {
      this.increaseCount()
    })
  }

  // 
}


هناك طريقة أسهل. يمكننا إنشاء زيادة العد كدالة سهم. في هذه الحالة ، سيشير هذا إلى المثال.



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  increaseCount = () => {
    this.#count = this.#count + 1
    this.updateCounter()
  }

  // ...
}


الرمز


إليك رمز المثال الكامل:







إنشاء عداد باستخدام FF


البداية متشابهة - نطلب من المستخدم البحث عن ترميز العداد واجتيازه:



function Counter (counter) {
  // ...
}

const counter = Counter(document.querySelector('.counter'))


نحصل على العناصر الضرورية ، والتي ستكون خاصة بشكل افتراضي:



function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')
}


لنبدأ بتهيئة المتغير "count":



function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')

  let count = parseInt(countElement.textContext)
}


ستتم زيادة قيمة العداد باستخدام طريقة "زيادة العد". يمكنك استخدام دالة عادية ، لكني أفضل طريقة مختلفة:



function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
    }
  }
}


سيتم تحديث DOM باستخدام طريقة "updateCount" التي تُدعى بداخل زيادة كونت:



function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
      counter.updateCount()
    },

    updateCount () {
      increaseCount()
    }
  }
}


لاحظ أننا نستخدم counter.updateCount بدلاً من this.updateCount.



إضافة معالج حدث


يمكننا إضافة معالج حدث إلى buttonElement باستخدام counter.increaseCount باعتباره رد اتصال.



سيعمل هذا لأننا لا نستخدم هذا ، لذلك لا يهمنا أن المعالج يغير سياق هذا.



function Counter (counterElement) {
  // 

  // 
  const counter = { /* ... */ }

  //  
  buttonElement.addEventListener('click', counter.increaseCount)
}


الميزة الأولى لهذا


يمكنك استخدام هذا في FF ، ولكن فقط في سياق الطريقة.



في المثال التالي ، سيؤدي استدعاء counter.increasCount إلى استدعاء counter.updateCount لأن هذا يشير إلى العداد:



function Counter (counterElement) {
  // 

  // 
  const counter = {
    increaseCount() {
      count = count + 1
      this.updateCount()
    }
  }

  //  
  buttonElement.addEventListener('click', counter.increaseCount)
}


ومع ذلك ، لن يعمل معالج الأحداث بسبب تغيير هذه القيمة. يمكن حل هذه المشكلة بالربط ، ولكن ليس بوظائف الأسهم.



الميزة الثانية لهذا


عند استخدام صيغة FF ، لا يمكننا إنشاء طرق في شكل وظائف السهم ، لأن الطرق يتم إنشاؤها في سياق الوظيفة ، أي سيشير هذا إلى النافذة:



function Counter (counterElement) {
  // ...
  const counter = {
    //   
    //  ,  this   window
    increaseCount: () => {
      count = count + 1
      this.updateCount()
    }
  }
  // ...
}


لذلك ، عند استخدام FF ، أوصي بشدة بتجنب استخدامه.



الرمز








حكم معالج الحدث


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



عند استخدام FF ، أوصي بالاستغناء عن هذا على الإطلاق.



خاتمة



لذلك ، في هذه المقالة ، نظرنا في أربع طرق لإنشاء كائنات في JavaScript:



  • وظائف المنشئ
  • الطبقات
  • ربط الأشياء
  • وظائف المصنع


أولاً ، توصلنا إلى استنتاج مفاده أن الفئات و FFs هي أفضل الطرق لإنشاء الكائنات.



ثانيًا ، رأينا أن إنشاء الفئات الفرعية أسهل باستخدام الفئات. ومع ذلك ، في حالة التركيب ، من الأفضل استخدام FF.



ثالثًا ، قمنا بتلخيص أنه عندما يتعلق الأمر بالتغليف ، فإن FFs لها ميزة على الفئات ، حيث تتطلب الأخيرة استخدام بادئة خاصة "#" ، و FFs تجعل المتغيرات خاصة تلقائيًا.



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



هذا كل شيء بالنسبة لي. أرجو أن تتمتع هذه المادة. شكرآ لك على أهتمامك.



All Articles