
تصوير
سيباستيان هيرمان .
يوم جيد يا اصدقاء!
أقدم لكم ترجمة مقال دانيال جيمس
"ما هذا؟" لماذا هذا؟ " .
ما هو "هذا" وماذا يأكل
عندما بدأت في تعلم جافا سكريبت ، بدا لي هذا الأمر مربكًا للغاية.
مقدمة
يمكن تفسير النمو السريع في شعبية JS جزئياً من خلال عتبة الدخول المنخفضة. ميزات مثل الوظائف وهذا عادة ما تعمل كما هو متوقع. من أجل أن تصبح محترفًا في JS ، لا تحتاج إلى معرفة الكثير من التفاصيل والتفاصيل الصغيرة (أود المجادلة مع ذلك - تقريبًا.). لكن مرة واحدة ، يواجه كل مطور خطأً ناتجًا عن قيمة هذا.
بعد ذلك ، لديك رغبة في فهم كيفية عمل هذا في JS. هل هذا مفهوم للبرامج الموجهة للكائنات (OOP)؟ هل JS لغة برمجة الكائنات (OOJP)؟ إذا كنت "google" هذا ، فستتلقى ردًا على ذكر بعض النماذج الأولية. أي نوع من النماذج؟ ما هي الكلمة الرئيسية "الجديدة" المستخدمة قبل ظهور الطبقات في JS؟
كل هذه الأشياء ترتبط ارتباطا وثيقا. ولكن قبل أن أشرح كيف يعمل هذا ، سوف أسمح لنفسي باستطراد صغير. أريد أن أتحدث قليلا عن سبب JS هو ما هو عليه.
OOP في JS
نموذج البرمجة النموذجية (الميراث) في JS هي واحدة من السمات المميزة ل OOP. حتى قبل ظهور فصول JS ، كان هناك OOJP. JS هي لغة بسيطة تستخدم أشياء قليلة فقط من OOP. وأهم هذه العناصر هي الوظائف والإغلاقات والنماذج والنماذج الحرفية للأشياء والكلمة الرئيسية الجديدة.
التغليف وإعادة الاستخدام مع الإغلاق
دعونا إنشاء فئة العداد. يجب أن يكون لدى هذه الفئة طرق لإعادة ضبط العداد وتزايده. يمكننا كتابة شيء مثل هذا:
function Counter(initialValue = 0){ let _count = initialValue return { reset: function(){ _count = 0 }, next: function(){ return ++_count } } } const myCounter = Counter() console.log(myCounter.next())
في هذه الحالة ، حصرنا أنفسنا في استخدام الدالات والحرف اليدوية للكائنات دون هذا أو جديد. نعم ، لقد حصلنا بالفعل على شيء من OOP. لدينا الفرصة لإنشاء مثيلات جديدة من Counter. كل مثيل من عداد لديه عدد المتغيرات الداخلية الخاصة به. نفذنا التغليف وإعادة استخدامها بطريقة وظيفية بحتة.
قضية الأداء
لنفترض أننا نكتب برنامجًا يستخدم عددًا كبيرًا من العدادات. سيكون لكل عداد إعادة تعيين الأساليب الخاصة به ثم التالي (Counter (). Reset! = Counter (). Reset). إنشاء مثل هذه الإغلاقات لكل طريقة من كل حالة سوف تتطلب قدرا هائلا من الذاكرة! مثل هذا الهيكل "غير مستدام". لذلك ، نحتاج إلى إيجاد طريقة للتخزين في كل حالة من حالات العداد ، وهي تشير فقط إلى الطرق التي يستخدمها (في الواقع ، هذا ما تفعله جميع OOJPs ، مثل Java).
يمكننا حل هذه المشكلة على النحو التالي (دون تضمين ميزات لغة إضافية):
let Counter = { reset: function(counter){ counter._count = 0 }, next: function(counter){ return ++counter._count }, new: function(initialValue = 0){ return { _count: initialValue } } } const myCounter = Counter.new() console.log(Counter.next(myCounter))
يعمل هذا النهج على حل مشكلة الأداء ، ولكن كان علينا حل وسط خطير ، والذي يتمثل في الحاجة إلى درجة عالية من مشاركة المبرمجين في تنفيذ البرنامج (في ضمان عمل الشفرة). بدون أدوات إضافية ، يجب أن نكون راضين عن هذا النهج.
هذا يسارع إلى الإنقاذ
نعيد كتابة مثالنا باستخدام هذا:
let Counter = { reset: function(){ this._count = 0 }, next: function(){ return ++this._count }, new: function(initialValue = 0){ return { _count: initialValue,
لاحظ أننا ما زلنا بصدد إعادة تعيين وظائف بسيطة والتالي (Counter.new (). Reset == Counter.new (). Reset). في المثال السابق ، لكي يعمل البرنامج ، اضطررنا إلى تقديم واصف مثيل للأساليب التي تم تنفيذها بشكل مشترك. الآن نحن فقط ندعو myCounter.next () ونشير إلى المثيل باستخدام هذا. ولكن كيف يعمل؟ إعادة التعيين والتالي يتم الإعلان عنها في كائن العداد. كيف يعرف JS ما يشير إليه عند استدعاء وظيفة؟
وظيفة الدعوة في JS
أنت تعلم جيدًا أن الوظائف في JS لها طريقة اتصال (هناك أيضًا طريقة للتطبيق ؛ والفرق بين هذه الطرق غير مهم. والفرق هو في كيفية تخطي المعلمات: في التطبيق كصفيف ، في المكالمة مفصولة بفواصل - تقريبًا لكل.) . باستخدام المكالمة ، تقرر ما الذي يعنيه هذا عند استدعاء الوظيفة:
const myCounter = Counter.new() Counter.next.call(myCounter)
هذا هو في الواقع ما يفعله تدوين النقاط خلف المشهد عندما نسميها الوظيفة. lhs.fn () مطابق لـ fn.call (lhs).
وبالتالي ، هذا معرف خاص يتم تعيينه عند استدعاء الوظيفة.
تبدأ المشاكل
لنفترض أنك تريد إنشاء عداد وتزيد قيمته كل ثانية. إليك كيفية القيام بذلك:
const myCounter = Counter.new() setInterval(myCounter.next, 1000)
هل ترى خطأ هنا؟ عند بدء setInterval ، تكون قيمة هذا غير محددة ، لذلك لا يحدث شيء. يمكن حل هذه المشكلة على النحو التالي:
const myCounter = Counter.new() setInterval(function(){ myCounter.next() }, 1000)
قليلا عن الربط
هناك طريقة أخرى لحل هذه المشكلة:
function bindThis(fn, _this){ return function(...args){ return fn.call(_this, ...args) } } const myCounter = Counter.new() setInterval(bindThis(myCounter.next, myCounter), 1000)
باستخدام دالة bindThis factory ، يمكننا أن نتأكد من أن Counter.next يستدعي دائمًا myCounter بهذا ، بغض النظر عن كيفية استدعاء الوظيفة الجديدة. في الواقع ، نحن لا نغير وظيفة Counter.next. لدى JS طريقة ربط مدمجة. لذلك ، يمكننا إعادة كتابة المثال أعلاه كما يلي: setInterval (myCounter.next.bind (myCounter) ، 1000).
نحن نعمل مع النماذج
في الوقت الحالي ، لدينا فئة عداد لطيفة ، لكنها لا تزال "ملتوية" قليلاً. هذه هي السطور التالية:
نحتاج إلى طريقة أفضل لمشاركة أساليب الفصل مع مثيلاته. النماذج الأولية تقوم بعمل ممتاز في هذا. إذا كنت تشير إلى خاصية دالة أو كائن غير موجود ، فستبحث JS عن هذه الخاصية في النموذج الأولي لهذه الوظيفة أو الكائن (ثم في النموذج الأولي للنموذج وهكذا إلى Object.prototype الموجود في أعلى سلسلة النموذج الأولي - تقريبًا. Trans.). يمكنك تحديد النموذج الأولي للكائن باستخدام Object.setPrototypeOf. دعنا نعيد كتابة فئة عدادنا باستخدام النماذج الأولية:
let Counter = { reset: function(){ this._count = 0 }, next: function(){ return ++this._count }, new: function(initialValue = 0){ this._count = initialValue } } function newInstanceOf(klass, ...args){
الكلمة الرئيسية "جديد"
إن استخدام setPrototypeOf مشابه جدًا لكيفية عمل المشغل "الجديد". الفرق هو أن الجديد سوف يستخدم مُنشئ النموذج الأولي للدالة التي تم تمريرها. لذلك ، بدلاً من إنشاء كائن لأساليبنا ، نمررها إلى النموذج الأولي لمنشئ الوظيفة:
function Counter(initialValue = 0){ this._count = initialValue } Counter.prototype.reset = function(){ this._count = 0 } Counter.prototype.next = function(){ return ++this._count } const myCounter = new Counter() console.log(`${myCounter.next()}`)
أخيرًا ، لدينا الكود بالشكل الذي يمكن العثور عليه به في التطبيق. قبل ظهور الفصول في JS ، كان هذا هو النهج القياسي لإنشاء وتهيئة الفصول.
الكلمة الرئيسية "الطبقة"
آمل أن تفهم الآن لماذا نستخدم النموذج الأولي لمنشئ الوظيفة وكيف يعمل هذا في أساليب الوظيفة. ومع ذلك ، يمكن تحسين رمز لدينا. لحسن الحظ ، يوجد اليوم في JS طريقة أفضل لإعلان الدروس:
class Counter { reset(){ this._count = 0 } next(){ return ++this._count } constructor(initialValue = 0){ this._count = initialValue } } const myCounter = new Counter() console.log(`${myCounter.next()}`)
الكلمة "class" لا تفعل شيئًا خاصةً تحت كلمة "cat". يمكنك التفكير في الأمر على أنه سكر نحوي ، وهو عبارة عن غلاف لمنهج "النموذج الأولي". إذا قمت بتشغيل الناقل الموجه إلى ES3 ، فستحصل على شيء مثل هذا:
var Counter = (function(){ function Counter(initialValue){ if(initialValue === void 0) { initialValue = 0 } this._count = initialValue } Counter.prototype.reset = function(){ this._count = 0 } Counter.prototype.next = function(){ ++this._count } return Counter }()); var myCounter = new Counter() console.log(myCounter.next())
لاحظ أن رمز transpiler أنشأ رمزًا متطابقًا تقريبًا مع المثال السابق.
وظائف السهم
إذا كنت تكتب رمزًا في JS منذ 5 سنوات ، فقد تتفاجأ من الإشارة إلى وظائف السهم. نصيحتي: دائما استخدام وظائف السهم حتى تحتاج حقا وظيفة منتظمة. لقد حدث أن تعريف المُنشئ وأساليب الفصل هو مجرد حالة يجب أن نستخدم فيها وظائف عادية. واحدة من ميزات وظائف السهم هو التشويش.
هذا في وظائف السهم
قد يفترض البعض أن وظائف السهم تأخذ القيمة الحالية لهذا عند إنشائها. هذا غير صحيح من الناحية الفنية (لم يتم تعريف معنى هذا ، فهو مأخوذ من البيئة المعجمية) ، ولكن هذا نموذج عقلي جيد. وظيفة السهم مثل هذا:
const myArrowFunction = () => { this.doSomething() }
يمكنك إعادة كتابتها مثل هذا:
const _this = this const myRegularFunction = function(){ _this.doSomething() }
شكرا لاهتمامكم كل التوفيق.