قد تبدو لغة جافا سكريبت JavaScript للوهلة الأولى سهلةً للغاية، إلا أن اللغة أكثر دقة وقوة وتعقيدًا مما قد تعتقده، إذ تؤدي العديد من التفاصيل الدقيقة في جافا سكريبت إلى عدد من المشكلات الشائعة - نناقش 10 منها في هذا المقال- حيث نركز على المشكلات التي تمنع التعليمات البرمجية من العمل على النحو المطلوب لتكون على دراية بهذه المخاطر وتتجنبها في سعيك لأن تصبح مطور جافا سكريبت محترفًا.
تعد جافا سكريبت في يومنا الحاليّ جوهرًا لمعظم تطبيقات الويب الحديثة تقريبًا. ولهذا السبب تقع مهمة اكتشاف مشاكل جافا سكريبت، والأخطاء التي تسببها في مقدمة اهتمامات مطوري الويب، كما أن اعتماد المكتبات والأطر القوية على جافا سكريبت لتطوير تطبيقات الصفحة الواحدة Single Page Applications أو اختصارًا SPA والرسومات والرسوم المتحركة ومنصات جافا سكريبت الموجودة من طرف الخادم ليس بالأمر الجديد، إذ أصبحت جافا سكريبت منتشرة في كل مكان في عالم تطوير تطبيقات الويب، وأصبح من المهمّ تعلّم لغة جافا سكريبت وإتقانها.
قد تبدو جافا سكريبت للوهلة الأولى بسيطة جدًا، وفي الواقع تعدّ عملية إنشاء وظائف جافا سكريبت الأساسية في صفحة ويب مهمة بسيطةً إلى حدّ ما لأي مطور برمجيات ذي خبرة، حتى لو كان جديدًا إلى جافا سكريبت، ومع ذلك فإن اللغة أكثر دقة وقوة وتعقيدًا مما قد يعتقده المرء في البداية؛ في الواقع، يمكن أن تؤدي العديد من التفاصيل الدقيقة لجافا سكريبت إلى عدد من المشكلات الشائعة التي تمنعها من العمل، وسنناقش 10 منها هنا. من المهم أن تكون على دراية بهذه المشكلات وتتجنبها أثناء رحلتك لتصبح مطور جافا سكريبت محترفًا.
مشكلة جافا سكريبت الأولى: المراجع المغلوطة لـ this
ستجد دومًا ارتباكًا بين مطوري جافا سكريبت فيما يتعلق بـكلمة جافا سكريبت المفتاحية this
مع تطوّر وزيادة تعقيد تقنيات برمجة جافاسكريبت وأنماط التصميم Design Patterns، بات هناك زيادة مقابلة في انتشار نطاقات المرجعية الذاتية
self-referencing scopes ضمن دوال رد النداء callbacks والمنغلقات closures، التي تعد المصدر الأساسي إلى حد ما للارتباك مع this
الذي يسبب مشاكل جافا سكريبت.
انظر إلى المثال التالي:
const Game = function() { this.clearLocalStorage = function() { console.log("Clearing local storage..."); }; this.clearBoard = function() { console.log("Clearing board..."); }; }; Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(function() { this.clearBoard(); // What is "this"? }, 0); }; const myGame = new Game(); myGame.restart();
تظهر نتائج تنفيذ الشيفرة البرمجية أعلاه الخطأ التالي:
Uncaught TypeError: this.clearBoard is not a function
لماذا؟ الأمر كله يتعلق بالسياق فسبب حدوث هذا الخطأ هو أنه عند استدعاء الدالة setTimeout()
، فإنك في الواقع تستدعي window.setTimeout()
. ونتيجة لذلك، تُعرَّف الدالة المجهولة anonymous function التي تُمرَّر إلى setTimeout()
في سياق كائن window
الذي لا يحتوي على تابع بالاسم ClearBoard()
.
الحل التقليدي لهذه المشكلة والمتوافق مع المتصفحات القديمة هو حفظ المرجع إلى this
في متغير يمكن بعد ذلك توريثه عبر دالة منغلقة closure function، على سبيل المثال:
Game.prototype.restart = function () { this.clearLocalStorage(); const self = this; // Save reference to 'this', while it’s still this! this.timer = setTimeout(function(){ self.clearBoard(); // Oh, OK, I do know who 'self' is! }, 0); };
ويمكن أن نستخدم حلًا مختلفًا في المتصفحات الأحدث وذلك يتضمن استخدام التابع ()bind كما يلي:
Game.prototype.restart = function () { this.clearLocalStorage(); this.timer = setTimeout(this.reset.bind(this), 0); // Bind to 'this' }; Game.prototype.reset = function(){ this.clearBoard(); // OK, back in the context of the right 'this'! };
مشكلة جافا سكريبت الثانية: الاعتقاد بوجود نطاق على مستوى كتلة شيفرة
أحد أسباب حدوث ارتباك شائع بين مطوري جافا سكريبت (وبالتالي مصدر شائع للأخطاء) هو افتراض أن جافا سكريبت تنشئ نطاقًا جديدًا لكل كتلة تعليمات برمجية. فبالرغم من أن هذا الأمر صحيح في العديد من اللغات الأخرى، إلا أنه ليس صحيحًا في لغة جافا سكريبت، خذ بعين الاعتبار الشيفرة البرمجية التالية:
for (var i = 0; i < 10; i++) { /* ... */ } console.log(i); // What will this output?
إن كنت تظن أن استدعاء console.log()
سيعطي خرجاً undefined
أو خطأً ما، فتخمينك خاطئ. صدق أو لا تصدق، سيكون الخرج هنا هو 10
. لماذا؟
في معظم اللغات البرمجية الأخرى، ستتسبب الشيفرة البرمجية السابقة في حدوث خطأ برمجي لأنّ دورة حياة المتغير (أو نطاقه) مقيّدةٌ ضمن كتلة for
. أما في لغة جافا سكريبت، فالمتغير i
يبقى داخل النطاق بعد اكتمال حلقة for
، محتفظاً بالقيمة الأخيرة بعد الخروج من الحلقة. (يُعرف هذا التصرف باسم رفع المتغيرات)
إن دعم النطاقات على مستوى الكتل موجود في جافا سكريبت عبر كلمة let
المفتاحية. وكلمة let
كانت مدعومةً بشكلٍ كبير من قبل المتصفحات وبيئنات تشغيل جافا سكريبت مثل Node.js منذ سنوات. وإن كان هذا جديداً عليك، حان الوقت لتتعلم المزيد حول نطاق المتغيرات scopes وكائنات prototype في جافا سكريبت.
مشكلة جافا سكريبت الثالثة: التسبب بتسريب الذاكرة
يعد تسرب الذاكرة memory leak مشكلةً حتمية تقريبًا في جافا سكريبت إن لم تكن تبرمج بتركيز لتجنبها، وهناك طرق عديدة لحدوث ذلك، ولكننا سنسلط الضوء فقط على اثنتين من أكثر حالات حدوثها شيوعًا.
تسريب الذاكرة - المثال1: المراجع المعلقة للكائنات المحذوفة
اقتباسملاحظة: ينطبق هذا المثال حصراً على محرّكات جافا سكريبت القديمة، أما الحديثة فتحوي على كانسات مهملات ذكية Garbage Collectors (أو اختصارًا) GCs بما يكفي لعلاج هذه المشكلة.
لنضع بعين الاعتبار هذه الشيفرة البرمجية:
var theThing = null; var replaceThing = function () { var priorThing = theThing; // Hold on to the prior thing var unused = function () { // 'unused' is the only place where 'priorThing' is referenced, // but 'unused' never gets invoked if (priorThing) { console.log("hi"); } }; theThing = { longStr: new Array(1000000).join('*'), // Create a 1MB object someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000); // Invoke 'replaceThing' once every second
إن نفّذت الشيفرة البرمجية السابقة وراقبت استخدام الذاكرة، ستجد أنّ لديك تسريباً كبيراً للذاكرة - حوالي ميغابايت في الثانية الواحدة! لدرجة أن كانسات القمامة لن تكون قادرة على المساعدة. ما يعني أنّه يتم تسريب longStr
في كلّ مرةٍ نستدعي فيها replaceThing
. ولكن لماذا؟
لنفحص الشيفرة بشكلٍ أدقّ:
يتضمن كلّ كائن theThing
على كائن longStr
بحجم ميغابايت واحد. عندما نستدعي replaceThing
في كلّ ثانية فإنّه يحتفظ بالمرجع الخاص بكائن theThing
السّابق داخل priorThing
، هل من الممكن أن تكون هذه هي المشكلة؟ ذلك من المستبعد، إذ يتم بشكل مسبق تحصيل dereference (أي عملية إلغاء المرجع) priorThing
في كلّ مرةٍ (عندما نعدّل قيمة priorThing
عبر السطر priorThing = theThing;
).
بالإضافة إلى أنه ذلك، فنحن نحصل على قيمة priorThing
داخل الدالة replaceThing
وفي تابع unused
، ما يعني أنه لم يُستخدم.
ما يعني أنّنا لم نجد الإجابة على سؤالنا حول تسريب الذاكرة هنا.
نحتاج إلى فهمٍ أعمق لآلية عمل جافا سكريبت الداخلية لفهم ما يحدث. عادةً ما تُطبَّق الدوال المغلّفة عبر كلّ دالة ضمن كائن متصل بكائنٍ بنمط القاموس الذي يمثّل النطاق المعجميّ lexical scope. إن استخدمت كلٌّ من الدالتين اللتين تم تعريفهما داخل replaceThing
تعليمة priorThing
، سيكون من المهمّ أن تحصل كلاهما على الكائن نفسه، حتى لو تم تعيين priorThing
مرارًا وتكرارًا بحيث تشترك كلتا الدالتين في البيئة المعجمية lexical environment (البيئة التي تحتوي على معلومات حول المتغيرات المحلية والعامة بالإضافة إلى الدوال المتاحة في نطاق البرنامج عند تعريف الدالة) ذاتها. ولكن ما إن تستخدم المتغيرات من قبل أيٍّ مغلّف، ستصل إلى البيئة المعجميّة المشتركة بين جميع المغلّفات في هذا النطاق. وهذا الفارق الدقيق هو ما يؤدي إلى تسريب الذاكرة هذا.
تسريب الذاكرة - المثال 2: المراجع الذاتية
لنرى هذا الجزء من الشيفرة البرمجيّة:
function addClickHandler(element) { element.click = function onClick(e) { alert("Clicked the " + element.nodeName) } }
لتعليمة onClick
هنا مغلّفًا يحافظ على مرجعٍ إلى element
(عبر element.nodeName
). وبإسناد onClick
إلى element.click
يُنشأ المرجع الدوار أي يصبح لدينا:
element
→onClick
→ element
→ onClick
→ element
…
ومن المثير للاهتمام أنّه حتّى إن أزلنا element
من DOM، فسيمنع المرجع الذاتي circular reference (الذي يشير لنفسه بشكل دوري) أعلاه من أن نحصل على قيمة element
و onClick
ما يؤدي إلى تسريب الذاكرة.
تجنّب تسريب الذاكرة: الأساسيّات
تعتمد إدارة ذاكرة جافا سكريبت (وبشكلٍ خاصّ كنس البيانات المهملة ) بشكلٍ كبير على الوصول إلى الكائن.
من المفترض أن تكون الكائنات التالية قابلة للوصول كما تعرف باسم "الجذور roots":
- الكائنات المشار إليها من أي مكان في مكدس الاستدعاءات call stack الحالي (أي جميع المتغيرات والمعاملات المحلية في الدوال التي تُستدعى حاليًا، وجميع المتغيرات في نطاق المغلّف).
- كلّ المتغيرات العامة.
يُحتفظ بالكائنات في الذاكرة طالما يمكن الوصول إليها من أي من الجذور من خلال مرجع أو سلسلة من المراجع.
يوجد في المتصفح أداة كنس مهملات تنظف الذاكرة التي تشغلها الكائنات غير القابلة للوصول؛ بمعنى آخر، تُزال الكائنات من الذاكرة فقط إذا اعتقد كانس المهملات أنه لا يمكن الوصول إليها. لسوء الحظ، من السهل أن تخلص بوجود كائنات "زومبي" قديمة لم تعد قيد الاستخدام ولكن لا يزال كانس المهملات يعتقد أنه يمكن الوصول إليها.
مشكلة جافا سكريبت الرابعة: عدم فهم المساواة
إحدى وسائل جافا سكريبت المرنة هي أنها ستحوّل أي قيمة في سياق بولياني إلى قيمة بوليانية تلقائيًا، ولكن في بعض الحالات يسبب ذلك إرباكًا بدلًا من توفير مرونة في التعامل، ومن المعروف أن التعبيرات التالية مزعجة للعديد من مطوري جافا سكريبت، على سبيل المثال:
// All of these evaluate to 'true'! console.log(false == '0'); console.log(null == undefined); console.log(" \t\r\n" == 0); console.log('' == 0); // And these do too! if ({}) // ... if ([]) // ...
على الرغم من كون العنصرين الأخيرين فارغين (مما قد يقودك إلى الاعتقاد بأنهما سيُقيَّمان إلى false
)، فإن كلاً من {} و [] هما في الواقع كائنان، وسيُحوَّل أي كائن قسريًا إلى قيمة بوليانيّة مساوية إلى true
في جافا سكريبت بما يتوافق مع معايير ECMA-262.
وكما توضح هذه الأمثلة، فإن قواعد تحويل النوع القسريّ يمكن أن يكون في بعض الأحيان صعب الفهم. وفقًا لذلك، ما لم تكن ترغب بذلك بشكل صريح، فمن الأفضل عادةً استخدام ===
و ==!
(بدلاً من ==
و =!
) لتجنب أي آثار جانبية غير مقصودة لتحويل النوع القسريّ. (==
و =!
ينفذان تحويل النوع تلقائيًا عند مقارنة شيئين، بينما ===
و ==!
يقومان بنفس المقارنة بدون تحويل النوع).
وبما أنّنا نتحدّث عن تحويل النوع القسري والمقارنة بين النوعين، من المهمّ ذكر أن مقارنة NaN
مع أيّ شيء (حتّى NaN
!) سيعيد دائمًا false
، بالتالي لن تستطيع استخدام عوامل المساواة (==
، ===
، !=
، !==
) لتحديد إذا ما كانت القيمة NaN
أم لا، وعليك أن تستخدم بدلاً من ذلك الدالة العامة المبنية مسبقًا ()isNaN
:
console.log(NaN == NaN); // False console.log(NaN === NaN); // False console.log(isNaN(NaN)); // True
مشكلة جافا سكريبت الخامسة: التلاعب بنموذج تمثيل المستند DOM غير الفعّال
تسهّل جافا سكريبت التعامل مع DOM بشكل نسبي (كإضافة العناصر وتعديلها وإزالتها)، ولكنها لا تفعل ذلك مع التشديد على القيام بذلك بكفاءة.
ومن أكثر الأمثلة شيوعًا هو الشيفرة البرمجية التي تضيف سلسلة من عناصر DOM واحدًا تلو الآخر، إذ تعد إضافة عنصر DOM عملية مكلفة، والتعليمات البرمجية التي تضيف عناصر DOM متعددة على التوالي غير فعالة ومن المحتمل ألا تعمل بشكلٍ جيد.
أحد البدائل الفعالة عند الحاجة إلى إضافة عناصر DOM متعددة هو استخدام أجزاء المستند فقط، ما يؤدّي إلى تحسين الكفاءة والأداء.
كمثال:
const div = document.getElementById("my_div"); const fragment = document.createDocumentFragment(); const elems = document.querySelectorAll('a'); for (let e = 0; e < elems.length; e++) { fragment.appendChild(elems[e]); } div.appendChild(fragment.cloneNode(true));
بالإضافة إلى تحسين الكفاءة لهذا النهج، فإن إنشاء عناصر DOM المرفقة أمر مكلف، في حين أن إنشائها وتعديلها أثناء فصلها ثم إرفاقها يؤدي إلى أداء أفضل بكثير.
مشكلة جافا سكريبت السادسة: الاستخدام الخاطئ لتعريف الدوالّ داخل حلقات for
لننظر إلى هذه الشيفرة البرمجيّة:
var elements = document.getElementsByTagName('input'); var n = elements.length; // Assume we have 10 elements for this example for (var i = 0; i < n; i++) { elements[i].onclick = function() { console.log("This is element #" + i); }; }
بناءً على الشيفرة البرمجيّة أعلاه، إن كان هناك 10 عناصر إدخال، فإن النقر على أيٍّ منها سيعرض "هذا هو العنصر #10"! لأنه عند استدعاء onclick
لأي من العناصر، ستكون حلقة for
المذكورة أعلاه قد اكتملت وستكون قيمة i
تساوي 10 (لجميع العناصر).
لنرى كيف يمكننا تصحيح مشكلة جافا سكريبت هذه لتحقيق السلوك المطلوب:
var elements = document.getElementsByTagName('input'); var n = elements.length; // Assume we have 10 elements for this example var makeHandler = function(num) { // Outer function return function() { // Inner function console.log("This is element #" + num); }; }; for (var i = 0; i < n; i++) { elements[i].onclick = makeHandler(i+1); }
في هذه النسخة المنقحة ننفّذ makeHandler
على الفور في كل مرة نمر فيها خلال الحلقة، وفي كل مرة يتم تلقي القيمة الحالية لـ i+1
وربطها بمتغير num
محدد النطاق تعيد الدالة الخارجية الدالة الداخلية (التي تستخدم أيضًا متغير num
المحدد هذا) ويتم تعيين onclick
الخاصة بالعنصر على تلك الدّالة الداخلية. وهذا يضمن أن كل نقرة تتلقى وتستخدم قيمة i
المناسبة (عبر المتغير num
المحدد).
مشكلة جافا سكريبت السابعة: الفشل في الاستفادة بشكل صحيح من الوراثة النموذجية
يفشل عددٌ كبيرٌ من مطوري جافا سكريبت في فهم ميزات الوراثة النموذجيّة بشكل كامل، وبالتالي الاستفادة منها بشكل كامل.
إليك مثالًا بسيطًا:
BaseObject = function(name) { if (typeof name !== "undefined") { this.name = name; } else { this.name = 'default' } };
يبدو ذلك واضحًا ومباشراً. إن أعطيت اسمًا، استخدمه، وإلّا فاضبط الاسم كـ "افتراضيّ default"، مثلاً:
var firstObj = new BaseObject(); var secondObj = new BaseObject('unique'); console.log(firstObj.name); // -> Results in 'default' console.log(secondObj.name); // -> Results in 'unique'
ولكن ماذا لو قمنا بهذا:
delete secondObj.name;
ثمّ سنحصل على:
console.log(secondObj.name); // -> Results in 'undefined'
ولكن أليس من الأفضل أن يعود هذا إلى "default"؟ يمكننا القيام بذلك بسهولة إذا عدلنا الشيفرة الأصلية للاستفادة من الوراثة النموذجية، على النحو التالي:
BaseObject = function (name) { if(typeof name !== "undefined") { this.name = name; } }; BaseObject.prototype.name = 'default';
في هذه النسخة، ترث تعليمة BaseObject
ملكيّة name
من كائن prototype
، الذي يوجد بشكلٍ افتراضيّ في تعليمة default
، وبالتالي، إذا استدعي الباني بدون اسم، فسيعيّن الاسم افتراضيًا عبر default
، وبالمثل إن أزيلت خاصية name
من نسخة BaseObject
، فستجرى عملية بحث في سلسلة النموذج الأولي واسترداد خاصية name
من كائن prototype
حيث تظل قيمته default
. والآن نحصل على:
var thirdObj = new BaseObject('unique'); console.log(thirdObj.name); // -> Results in 'unique' delete thirdObj.name; console.log(thirdObj.name); // -> Results in 'default'
مشكلة جافا سكريبت الثامنة: إنشاء مراجع خاطئة لتوابع النُّسَخ Instance Methods
لنعرّف كائنًا بسيطًا وننشئ نسخةً له كما يلي:
var MyObjectFactory = function() {} MyObjectFactory.prototype.whoAmI = function() { console.log(this); }; var obj = new MyObjectFactory();
والآن لننشئ مرجعاً للتابع whoAmI
لضمان سهولة العمل ولنتمكن من الوصول إليه فقط من خلال whoAmI()
بدلاً من التابع ()obj.whoAmI
الأطول:
var whoAmI = obj.whoAmI;
وللتأكّد؛ خزّنّا مرجعًا للدالة، لنطبع القيمة لمتغيّرنا الجديد whoAmI
:
console.log(whoAmI);
الخرج:
function () { console.log(this); }
يبدو الأمر جيدًا حتّى الآن.
ولكن لنرى الفرق عندما نستدعي ()obj.whoAmI
بدلاً من مرجع السهولة الخاص بنا ()obj.whoAmI
:
obj.whoAmI(); // Outputs "MyObjectFactory {...}" (as expected) whoAmI(); // Outputs "window" (uh-oh!)
ما الخطأ الذي حصل؟ استدعينا التابع ()obj.whoAmI
في مساحة الاسم العامة، لذا فإن this
تُضَمّ إلى window
(أو في الوضع الدّقيق إلى undefined
)، وليس إلى نسخة obj
الخاصة بكائن MyObjectFactory
! بمعنًى آخر، قيمة this
تعتمد على سياق استدعائها.
تقدم الدوالّ السهمية {} < = (params
) بدلاً من {} (function (params
this
ثابتة وهذا ليس مبنيًّا على سياق الاستدعاء مثل this
للدوالّ الطبيعية، مما يمنحنا الحلّ البديل:
var MyFactoryWithStaticThis = function() { this.whoAmI = () => { // Note the arrow notation here console.log(this); }; } var objWithStaticThis = new MyFactoryWithStaticThis(); var whoAmIWithStaticThis = objWithStaticThis.whoAmI; objWithStaticThis.whoAmI(); // Outputs "MyFactoryWithStaticThis" (as usual) whoAmIWithStaticThis(); // Outputs "MyFactoryWithStaticThis" (arrow notation benefit)
من الممكن أنّك لاحظت أنّه على الرغم من حصولنا على خرج مطابق إلا أن this
هي مرجع لدالة المصنع factory التي تنشئ الكائن وليس مرجعًا إلى نسخة الكائن نفسها وبدلاً من محاولة إصلاح هذه المسألة، من المفيد التفكير في طرق التعامل مع جافا سكريبت التي لا تعتمد على this
(أو حتى new
) على الإطلاق.
مشكلة جافا سكريبت التاسعة: استخدام سلسلة كوسيط أول لدالة setTimeout
أو setInterval
بدايةً؛ لنوضّح أمرًا ما: استخدام سلسلة كوسيط أول لدالة setTimeout
أو setInterval
ليس خطأً بحدّ ذاته. فهو شيفرةٌ برمجيّةٌ صحيحةٌ في جافا سكريبت، والمشكلة هنا تتعلق بالأداء والكفاءة. ما يتمّ التغاضي عنه غالبًا هو أنّه إن مررت سلسلة كوسيط أول إلى setTimeout
أو setInterval
، فإنها ستمرر إلى باني الدالة لتحوَّل إلى دالة جديدة، ويمكن أن تكون هذه العملية بطيئة وغير فعالة، ونادرًا ما تكون ضرورية.
البديل لتمرير سلسلة كوسيط أول لهذه التوابع هو تمرير دالة بدلاً من ذلك. لنلقي نظرة على المثال التالي، هنا سنستخدم setInterval
و setTimeout
استخدامهما الاعتيادي، حيث نمرر سلسلة كمعامل أول:
setInterval("logTime()", 1000); setTimeout("logMessage('" + msgValue + "')", 1000);
ويكون الخيار الأفضل في هذه الحالة هو تمرير دالة كوسيط أولي مثل:
setInterval(logTime, 1000); // Passing the logTime function to setInterval setTimeout(function() { // Passing an anonymous function to setTimeout logMessage(msgValue); // (msgValue is still accessible in this scope) }, 1000);
مشكلة جافا سكريبت العاشرة: الفشل في استخدام الوضع الصارم
الوضع الصارم strict mode (بما في ذلك استخدام ;'use strict'
في بداية ملفات جافا سكريبت المصدرية) هو وسيلة لفرض تحليل أكثر دقّة ومعالجة الأخطاء في الشيفرة البرمجية الخاصة بك في وقت التنفيذ أيضًا. كوسيلة لجعلها أكثر أمانًا. ورغم أن الفشل في استخدام الوضع الصارم لا يشكل "خطأً" حقيقيًا، إلا أنّ استخدامه يتزايد بشكلٍ كبير ويتم تشجيعه بشكلٍ كبير، كما يعدّ تجاهله أمرًا سيّئًا.
هذه بعض الميّزات للوضع الصارم:
- يجعل تصحيح الأخطاء أسهل. يؤدي وجود الأخطاء في الشيفرة البرمجية التي كان من الممكن تجاهلها أو التي كانت ستفشل بصمت إلى إنشاء أخطاء أو رمي استثناءات، ما ينبهك بشكلٍ مسبق إلى مشاكل جافا سكريبت في مشروعك البرمجي ويوجهك بشكل أسرع إلى مصدرها.
- يمنع المتغيّرات العامة العرضية. يؤدي تعيين قيمة لمتغير غير معلن عنه تلقائيًا دون الوضع الصارم إلى إنشاء متغير عام بهذا الاسم. وهذا هو أحد أكثر أخطاء جافا سكريبت شيوعًا. وعند تفعيل الوضع الصارم يؤدي ذلك إلى إظهار خطأ.
-
يوقف تغيير النوع التلقائي لـ
this
. بدون الوضع الصارم، يفرض مرجع إلى قيمةthis
الخالية أو غير المحددة تلقائيًا إلى المتغيرglobalThis
. يمكن أن يسبب هذا العديد من الأخطاء. بينما في الوضع الصارم، تؤدي الإشارة إلى هذه القيمة الخالية أو غير المحددة إلى ظهور خطأ. -
يمنع تكرار أسماء الخاصيات أو قيم المعاملات. يظهر الوضع الصارم خطأً عند تحديد اسمين مماثلين لخاصية كائنٍ واحد (مثال؛
var object = {foo: “bar”, foo: “baz”};
) أو اسمًا مكرّرًا لدالّة (مثال؛function foo(val1, val2, val1){}
)، وبالتالي يساعد باكتشاف ما يكاد أن يكون خطأً في شيفرتك الذي ربما تكون قد أهدرت وقتًا طويلاً في تعقبه. -
يجعل
()eval
أكثر أمانًا. هناك بعض الاختلافات في الطريقة التي تتصرف بها الدالة()eval
بين الوضعين الصارم وغير الصارم . والأهم من ذلك، في الوضع الصارم، لا يتم تُنشئ المتغيرات والدوال المعرّفة داخل تعليمة()eval
في النطاق المحتوي. (بل تُنشأ في النطاق المحتوي في الوضع غير الصارم، الذي يمكن أن يكون أيضًا مصدرًا شائعًا لمشاكل جافا سكريبت). -
يعطي خطأً عند استخدامٍ خاطئٍ لـ
delete
. لا يمكن استخدام عاملdelete
(الذي يستخدم لإزالة الخواص من الكائنات) على خواص كائنات غير قابلة للضبط nonconfigurable properties. ستفشل التعليمات البرمجية غير الصارمة بصمت عند إجراء محاولة لحذف خاصيةٍ غير قابلة للضبط، في حين أن الوضع الصارم سيعطي رسالة خطأ في هذه الحالة.
التخفيف من مشاكل جافا سكريبت باستخدام نهج أكثر ذكاءً
كما هو الحال مع أي تقنية، كلما فهمت كيفية عمل جافا سكريبت بشكلٍ أفضل، كلما كانت التعليمات البرمجية الخاصة بك أكثر اعتمادية، وكلما تمكنت من تسخير قوة اللغة بشكل أكثر فعالية. بالمقابل، فإن الافتقار إلى الفهم الصحيح لنماذج ومفاهيم جافا سكريبت هو المكان الذي تكمن فيه العديد من مشاكلها، والتعرف جيدًا على الفروق الدقيقة في اللغة وخفاياها هو الاستراتيجية الأكثر فعالية لتحسين كفاءتك وزيادة إنتاجيتك.
أسئلة شائعة
لنختم مقالنا بجملة من الأسئلة الشائعة حول جافاسكريبت:
ما هي الأخطاء الشائعة في جافا سكريبت؟
الأخطاء الشائعة التي يرتكبها المطورون خلال البرمجة باستخدام جافا سكريبت تتضمن الفهم الخاطئ لكيفية عمل كلمة this
المفتاحية، والافتراض الخاطئ حول النطاق الكتلي والفشل في تفادي تسريبات الذاكرة. تسببت مسيرة تطوير جافا سكريبت عبر الزمن بالعديد من الأخطاء في حال الاعتماد على أنماط البرمجة القديمة.
كيف يمكنني تطوير مهاراتي في جافا سكريبت؟
يمكنك تطوير مهاراتك في جافا سكريبت عن طريق الالتزام بالممارسات المثلى عند كتابة الشيفرة البرمجية، وقراءة التفاصيل الخفية في لغة البرمجة لتكون على علم بمزاياها المتقدمة وأوجه قصورها.
لمَ أكتب شيفرة برمجية معرضة بالأخطاء؟
قد تبدو الشيفرة البرمجية المعرضة للأخطاء على ما يرام عند النظر إليها للوهلة الأولى، إلا أنه وبتعلمك لهذه المشاكل الشائعة في جافا سكريبت فستبدي فهمًا أكبر للأنماط البرمجية التي تتسبب بالمشاكل وتتعلم طرق تفاديها في شيفرتك البرمجية.
هل جافا سكريبت كثيرة المشاكل؟
قد يزعم البعض أن لغة جافا سكريبت بنفسها مسببة للمشاكل، وعلى الرغم من أنها تشكو من بعض أوجه القصور إلا أنها شائعة ومنتشرة في كل مكان، لذا تعلّم كيفية اكتشاف أخطائها وتصحيحها أمر يستحق الجهد إذا كنت ستعمل معها (وهذا هو الحال مع معظم المطوّرين في يومنا الحالي).
ترجمة -وبتصرف- لمقال The 10 Most Common JavaScript Issues Developers Face لكاتبه Ryan J. Peterson
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.