أظهرت لنا السنوات القليلة الماضية أن تطوير الويب يتغير. ومع إضافة العديد من الميزات وواجهات برمجة التطبيقات إلى المتصفحات، كان علينا استخدامها بالطريقة الصحيحة. كانت اللغة التي ندين لها بهذا الشرف هي JavaScript.
في البداية، لم يكن المطورون مقتنعين في البداية بكيفية تصميمها وكان لديهم انطباعات سلبية في الغالب أثناء استخدام هذا البرنامج النصي. وبمرور الوقت، اتضح أن هذه اللغة تتمتع بإمكانيات كبيرة، كما أن معايير ECMAScript اللاحقة جعلت بعض الآليات أكثر إنسانية، وببساطة، أفضل. في هذا المقال، نلقي نظرة على بعضها.
أنواع القيم في JS
الحقيقة المعروفة عن JavaScript هو أن كل شيء هنا هو كائن. حقًا، كل شيء: المصفوفات، والدوال، والسلاسل، والأرقام، وحتى المنطقيات. جميع أنواع القيم ممثلة بكائنات ولها طرقها وحقولها الخاصة بها. ومع ذلك، يمكننا تقسيمها إلى فئتين: الأوليات والهيكليات. قيم الفئة الأولى غير قابلة للتغيير، مما يعني أنه يمكننا إعادة تعيين متغير ما بالقيمة الجديدة ولكن لا يمكننا تعديل القيمة الحالية نفسها. الفئة الثانية تمثل القيم التي يمكن تغييرها، لذا يجب تفسيرها على أنها مجموعات من الخصائص التي يمكننا استبدالها أو استدعاء الطرق المصممة للقيام بذلك.
نطاق المتغيرات المعلنة
قبل أن نتعمق أكثر، دعنا نشرح معنى النطاق. يمكننا القول أن النطاق هو المجال الوحيد الذي يمكننا فيه استخدام المتغيرات المعلنة. قبل معيار ES6 كان بإمكاننا قبل معيار ES6 أن نعلن عن المتغيرات باستخدام عبارة var ونعطيها نطاقًا عالميًا أو محليًا. الأول هو المجال الذي يسمح لنا بالوصول إلى بعض المتغيرات في أي مكان من التطبيق، أما الثاني فهو مخصص فقط لمنطقة محددة - دالة بشكل أساسي.
منذ معيار ES2015, JavaScript ثلاث طرق لتعريف المتغيرات التي تختلف بالكلمة المفتاحية. الطريقة الأولى موصوفة من قبل: المتغيرات المعلنة بواسطة الكلمة المفتاحية var يتم تحديد نطاقها إلى جسم الدالة الحالي. سمح لنا معيار ES6 بتعريف المتغيرات بطرق أكثر إنسانية - على عكس عبارات var، المتغيرات المعلنة بواسطة عبارات const و let يتم تحديد نطاقها إلى الكتلة فقط. ومع ذلك، تتعامل JS مع عبارة const بشكل غير اعتيادي تمامًا إذا ما قورنت بعبارات لغات البرمجة - بدلًا من القيمة الثابتة، فإنه يحتفظ بمرجع ثابت للقيمة. باختصار، يمكننا تعديل خصائص كائن معلن بعبارة const، لكن لا يمكننا الكتابة فوق مرجع هذا المتغير. يقول البعض أن البديل var في ES6 هو في الواقع عبارة let. لا، ليست كذلك، وعبارة var ليست كذلك وربما لن يتم سحبها أبدًا. الممارسة الجيدة هي تجنب استخدام عبارات var، لأنها في الغالب تسبب لنا المزيد من المشاكل. بالمقابل، علينا إساءة استخدام عبارات const، حتى نضطر إلى تعديل مرجعها - عندها يجب أن نستخدم let.
مثال على سلوك النطاق غير المتوقع
لنبدأ بما يلي الكود:
(() => {
بالنسبة إلى (var i = 0؛ i { {
console.log(``قيمة "i": ${i});
}, 1000);
}
})();
عندما ننظر إلى ذلك، يبدو أن حلقة التكرار تقوم بتكرار قيمة i، وبعد ثانية واحدة، ستسجل قيم المُكرِّر 1, 2, 3, 4, 5. حسنًا، لا يحدث ذلك. كما ذكرنا أعلاه، عبارة var هي عبارة عن الاحتفاظ بقيمة المتغيّر لكل جسم الدالة؛ يعني أنّه في التكرار الثاني والثالث وهكذا سيتم استبدال قيمة المتغيّر i بقيمة تالية. وأخيرًا، تنتهي الحلقة وتظهر لنا علامات المهلة ما يلي: 5، 5، 5، 5، 5، 5، 5، 5. أفضل طريقة للاحتفاظ بالقيمة الحالية للمُكرِّر هي استخدام عبارة Let بدلاً من ذلك:
(() => {
ل (دع i = 0؛ i { {
console.log(``قيمة "i": ${i});
}, 1000);
}
})();
في المثال أعلاه، نحتفظ بنطاق قيمة i في كتلة التكرار الحالية، فهو المجال الوحيد الذي يمكننا فيه استخدام هذا المتغير ولا يمكن تجاوزه من خارج هذا المجال. النتيجة في هذه الحالة هي كما هو متوقع: 1 2 3 3 4 5. لنلقِ نظرة على كيفية التعامل مع هذه الحالة باستخدام عبارة var:
(() => {
بالنسبة إلى (var i = 0؛ i {
SetTimeout(()) => { {
console.log(``قيمة "j": ${j}`);
}, 1000);
})(i);
}
})();
بما أن عبارة var تتعلق بالاحتفاظ بالقيمة داخل كتلة الدالة، علينا استدعاء دالة مُعرَّفة تأخذ وسيطة - قيمة الحالة الحالية للمُعرِّف - ثم نفعل شيئًا ما. لن يتجاوز أي شيء خارج الدالة المعلنة قيمة ي.
أمثلة على التوقعات الخاطئة لقيم الكائنات
الجريمة الأكثر ارتكابًا التي لاحظتها تتعلق بتجاهل قوة البنيات وتغيير خصائصها التي يتم تعديلها أيضًا في أجزاء أخرى من التعليمات البرمجية. ألقِ نظرة سريعة:
const DEFAULT_VALUE = {
الفرقة الموسيقية المفضلة: 'The Weeknd'
};
const currentValue = DEFAULT_VALUE;
const bandInput = document.querySelector('#favorite-band');
const restoreDefaultButton = document.querySelector('#restore-button')؛
النطاقInput.addEventListener('input', () => { {
currentValue.favoriteBand = bandInput.value;
)، خطأ);
زر الاستعادةDefaultButton.addEventListener('click', () => { {
currentValue = DEFAULT_VALUE;
}، خطأ);
من البداية: لنفترض أن لدينا نموذج بخصائص افتراضية، مخزنة ككائن. نريد أن يكون لدينا زر يعيد قيم المدخلات إلى القيم الافتراضية. بعد ملء المدخلات ببعض القيم، نقوم بتحديث النموذج. بعد لحظة، نعتقد أن الاختيار الافتراضي كان أفضل ببساطة، لذا نريد استعادته. نضغط على الزر... ولا يحدث شيء. لماذا؟ بسبب تجاهل قوة القيم المرجعية.
هذا الجزء: const currentValue = DEFAULTVALUE هو إخبار JS بما يلي: خذ المرجع إلى DEFAULTقيمة VALUE وتعيين متغير CurrentValue معها. يتم تخزين القيمة الحقيقية في الذاكرة مرة واحدة فقط وكلا المتغيرين يشير إليها. تعديل بعض الخصائص في مكان ما يعني تعديلها في مكان آخر. لدينا بعض الطرق لتجنب مثل هذه الحالات. إحدى هذه الطرق التي تفي باحتياجاتنا هي عامل الانتشار. دعنا نصلح شيفرتنا:
const DEFAULT_VALUE = {
الفرقة الموسيقية المفضلة: 'The Weeknd'
};
const currentValue = { ...DEFAULT_VALUE };
const bandInput = document.querySelector('#favorite-band');
const restoreDefaultButton = document.querySelector('#restore-button')؛
bandInput.addEventListener('input', () => { {
currentValue.favoriteBand = bandInput.value;
)، خطأ);
زر الاستعادةDefaultButton.addEventListener('click', () => { {
currentValue = { ...DEFAULT_VALUE };
}، خطأ);
في هذه الحالة، يعمل مُشغِّل السبريد على النحو التالي: يأخذ جميع الخصائص من كائن ما وينشئ كائنًا جديدًا مملوءًا بها. وبفضل هذا، لا تعود القيم في القيمة الحالية و DEFAULT_VALUE تشير إلى نفس المكان في الذاكرة ولن تؤثر جميع التغييرات المطبقة على إحداها على الأخرى.
حسنًا، السؤال هو: هل الأمر كله يتعلق باستخدام عامل الانتشار السحري؟ في هذه الحالة - نعم، ولكن قد تتطلب نماذجنا تعقيدًا أكثر من هذا المثال. في حالة استخدامنا كائنات متداخلة أو مصفوفات أو أي هياكل أخرى، فإن عامل الانتشار للقيمة المشار إليها من المستوى الأعلى سيؤثر فقط على المستوى الأعلى وستظل الخصائص المشار إليها تتشارك نفس المكان في الذاكرة. هناك العديد من الحلول للتعامل مع هذه المشكلة، كل ذلك يعتمد على احتياجاتك. يمكننا استنساخ الكائنات في كل مستوى عمق أو، في العمليات الأكثر تعقيدًا، استخدام أدوات مثل Immer التي تسمح لنا بكتابة شيفرة غير قابلة للتغيير دون عناء تقريبًا.
اخلطها كلها معًا
هل يمكن استخدام مزيج من المعرفة حول النطاقات وأنواع القيم؟ بالطبع هو كذلك! دعنا نبني شيئًا يستخدم كلاهما:
const useValue = (defaultValue) => {
const value = [...defaultValue];
كونت setValue = (قيمة جديدة) => { {
value.length = 0؛ // طريقة صعبة لمسح المصفوفة
newValue.forEach((عنصر، فهرس)) => { {
القيمة [الفهرس] = العنصر;
});
// القيام ببعض الأشياء الأخرى
};
إرجاع [القيمة، SetValue];
};
const [الحيوانات, setAnimals] = useValue(['cat', 'dog']);
وحدة التحكم.log(الحيوانات)؛ // ['قطة'، 'كلب']
setAnimals(['حصان'، 'بقرة']);
وحدة التحكم.log(الحيوانات)؛ // ['حصان'، 'بقرة']);
لنشرح كيف تعمل هذه الشيفرة سطراً بسطر. حسنًا، تقوم الدالة useValue بإنشاء مصفوفة بناءً على الوسيطة defaultValue؛ فهي تنشئ متغيرًا ودالة أخرى، وهي مُعدِّلها. يأخذ هذا المُعدِّل قيمةً جديدةً تُطبَّق بطريقةٍ مخادعة على القيمة الموجودة. في نهاية الدالة، نعيد القيمة ومعدِّلها كقيم مصفوفة. بعد ذلك، نستخدم الدالة التي تم إنشاؤها - نُعرِّف الحيوانات و setAnimals كقيم مُرجَعة. استخدم المُعدِّل الخاص بهم للتحقق مما إذا كانت الدالة تؤثر على متغير الحيوانات - نعم، إنها تعمل!
لكن انتظر، ما هو الشيء الرائع في هذه الشيفرة بالضبط؟ يحتفظ المرجع بجميع القيم الجديدة ويمكنك إدخال منطقك الخاص في هذا المُعدِّل مثل بعض واجهات برمجة التطبيقات أو جزء من النظام البيئي التي تشغل تدفق بياناتك دون أي جهد. غالبًا ما يُستخدم هذا النمط المخادع في مكتبات JS الأكثر حداثة، حيث يسمح لنا النموذج الوظيفي في البرمجة بإبقاء الشيفرة أقل تعقيدًا وأسهل في القراءة من قبل المبرمجين الآخرين.
الملخص
إن فهمنا لكيفية عمل ميكانيكا اللغة تحت الغطاء يسمح لنا بكتابة شيفرة أكثر وعيًا وخفة في الوزن. حتى لو لم تكن JS لغة منخفضة المستوى وتجبرنا على امتلاك بعض المعرفة حول كيفية تعيين الذاكرة وتخزينها، لا يزال علينا الانتباه للسلوكيات غير المتوقعة عند تعديل الكائنات. من ناحية أخرى، إساءة استخدام استنساخ القيم ليست دائمًا الطريقة الصحيحة والاستخدام غير الصحيح له سلبيات أكثر من الإيجابيات. الطريقة الصحيحة لتخطيط تدفق البيانات هي التفكير فيما تحتاجه والعقبات المحتملة التي يمكن أن تواجهها عند تنفيذ منطق التطبيق.
اقرأ المزيد: