جدول المحتويات

, , , , ,

أخطاء جافاسكربت عليك تجنّبها

إذا كنت جديداً على جافاسكربت – سواء كنت تكتب سكربتات صَرِفَة أو تستخدم إطار عمل (jQuery, Mootools, Dojo, YUI) معها، فعليك تفادي بعض الأخطاء. هذه قائمة بالأخطاء التي قد تقع بها كمبتدئ:

إشارة المساواة (=)

قد تعلم أنّ للمقارنة في جافاسكربت طريقتان. الأولى استخدام إشارتي مساواة ( == ). بهذه الطريقة تقارن القيمة، لكنها لا تقارن نوع البيانات. فمثلاً، إذا قارنت بين الرقم 1 و قيمة true فستكون النتيجة صحيحة (true)

if( 1 == true ) {
   //this code will run
}

هنا أمثلة أخرى:

1 == "1"        //true
"true" == true  //false
1 == true       //true
"0" == 0        //true
"" == 0         //true
" " == 0        //true
"Str" == false  //false
"Str" == true   //false

بعض النتائج هنا لا يتوقّعها من لا يعلم كيف تتعامل جافاسكربت مع علامة ( == ). كلّ المعاملات هنا – مهما كان نوع بياناتها – يتمّ تحويلها إلى قيمتها النصّية قبل المقارنة.

فلنأخذ المثال الأوّل (1==”1”). القيمة الأولى رقم بالأساس، لذا لا يتمّ تحويلها، ولكن المدخل الثاني من نوع “نصّيّ”، لذا يتمّ تحويله أو قراءته على أنّه رقم. النصّ “1” يتمّ تحويله إلى الرقم 1.

المخرجات في المثال الثاني false، وذلك لأنّه عند محاولة تحويل قيمة سلسلة نصّية فيها محارف وليس أرقامًا، فإن التحويل سينتج عنه NaN والتي تعني “ليس رقماً” أو Not a Number. إذا قورن أيّ شيء بـ Nan هكذا فستكون النتيجة false.

يمكنك معرفة نتيجة التحويل إلى رقم عن طريق تعريفها بـ Number. هنا بعض الاختبارات في Firebug:

قد تتساءل الآن عن كيفيّة استخدام ثلاثة إشارات مساواة هكذا ( === ). تستخدم هذه الطريقة لمقارنة نوع البيانات أيضاً، وليس قيمتها وحسب. إذا كان نوع المدخلين مختلفاً، فستكون النتيجة false دائماً. يجب أن يكون النوع والقيمة متساويين في المدخَلَين لتعطيك هذه المقارنة true.

4 === 4         //true
"2" === 2       //false
1 === true       //false
"true" === true //false

عدم إعطاء القيمة null للأنواع المرجعيّة

من الأخطاء الشائعة التي يقع فيها العديد من مطوّري ومبرمجي جافاسكربت عدم إعطاء القيمة null للأنواع المرجعيّة كالمصفوفات والكائنات عند الانتهاء من استخدامها. ألق نظرة على المثال التالي:

var arr = [1, 2, 3];
 
//perform some processing on arr
 
//assign null to arr when its use is finished
arr = null;

من فوائد إعطاء الأنواع المرجعيّة قيمة null استعادةُ مجمع القمامة (في محرّك جافاسكربت) الذاكرةَ المستخدمة في ذاك المتغيّر تلقائيًّا. اعلم أنّ هذا مهمّ جدًّا للمتغيرات ذات المحتوى الكبير، كالمتغيّرات العامّة. تزال المتغيّرات المحلّيّة تلقائيًّا عند خروجها من نطاق الاستخدام (خاصّة إذا كان محرّك جافاسكربت بمجمع قمامة Mark أو Sweep).

بَدء متغيّر مرجعيّ

لا تحاول إنشاء أكثر من متغيّر مرجعيّ في عِبارة واحدة. لاحظ المثال:

var arr1 = [1, 2, 3],
	arr2 = ['a', 'b', 'c'];
 
//reset both arrays
arr1 = arr2 = [];
 
//add a single item in arr2 and arr1
arr2.push( 32 );
arr1.push( 10 );
 
//print both arrays and you will see same result
//OUTPUT: 10, 32
alert( arr1.join() );
alert( arr2.join() );

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

يُضاف الرقم 32 إلى المصفوفة الثانية arr2 في السطر الثامن، ثمّ يضاف الرقم 10 إلى المصفوفة الأولى في السطر التاسع. لفحص قيم كلتي المصفوفتين استخدمنا join لكلّ منهما في السطرين الثالث عشر والرابع عشر. لاحظ أنّ المصفوفتين تحويان نفس القيم!

لا تَنْسَ الكلمة المفتاحيّة var

يمكنك الإعلان عن متغيّرات في جافاسكربت باستخدام var، ويمكنك أيضاً استخدام المتغيرات دون الإعلان عنها. هناك اختلاف هامّ بين هاتين الطريقتين في استخدام المتغيّرات. انظر إلى المثال التالي:

function createVar() {
       var myVar = 'local';
};
 
alert( myVar ); //output: undefined

كما لاحظت في المثال السابق فإنّ تعريف متغيّر باستخدام var يجعل الوصول إليها من خارج الدالّة غير ممكن. فلنجرّب الآن الإعلان عن متغير دون استخدام var

function createVar() {
       myVar = 'local';
};
 
alert( myVar ); //output: local

لاحظ أنّ المتغير المعرف دون var يمكن استخدامه من خارج الدالّة أيضاً، أي أنّ var يجعل المتغيّرات محلّيّة. لهذا انتبه عند استخدام متغيّرات في جافاسكربت. دائماً عرّف متغيّراتك باستخدام var.

عدم استخدام الإعلان عن أحداث

إرفاق معالج أحداث سهل في جافاسكربت. الكود في المثال التالي يضيف للوسم الذي يحمل id قيمته myLink متحكّماً بالنقر:

document.getElementById('myLink').addEventListener( 'click', function() {
  //you code goes here...
}, false );

افترض أنّك تريد إضافة متحكّم بالنقرات إلى كلّ عناصر td في الجدول. هل ستقوم بإضافة متحكّم لكلّ عنصر td في هذا الجدول؟

<table id="myTable">
  <tbody>
     <tr>
        <td>1, 1</td>
        <td>1, 2</td>
     </tr>
 
     <tr>
        <td>2, 1</td>
        <td>2, 2</td>
     </tr>
  </tbody>
</table>

سيساعدنا مندوبو الحدث. سنضيف هنا متحكّماً بالأحداث من نوع “نقرة مفردة” إلى myTable وسنتعامل من خلاله مع عناصر td لنعرف إذا نقرت أم لا. في هذه الحالة ليس علينا إضافة متحكّم بالأحداث لكلّ عنصر td في الجدول فيما يعرف بمندوب الحدث. هذا مثال:

document.getElementById( 'myTable' ).addEventListener( 'click', function( e ) {
     if( e.target && e.target.nodeName == 'TD' ) {
        console.log( e.target.innerHTML );
 
        //to access id
        //console.log( e.target.id );
 
        //to access className
        //console.log( e.target.className );
     }
  }, false );

عدم التفريق بين innerText و innerHTML

لا يميّز المطوّرون الجدد في العادة بين خصائص innerHTML و innerText. يستخدم كلا الإثنين مع العناصر element objects. يتعامل innerHTML مع كود html داخل العنصر، بينما يتعامل innerText مع النصّ داخل العنصر.

النصّ تحت Output مخرجات الأمر السابق. لاحظ أنّ وسوم HTML (في هذه الحالة وسم الفقرة <p>) تُضَمَّن في المخرجات.

لنلقِ نظرة الآن على مخرجات innerText

كما ترى في المثال أعلاه، يأخذ innerText النصّ داخل العنصر دون وسوم html الموجودة معه. {لاحظ اختفاء وسم الفقرة هنا}

إضافة العُقَد بكمية كبيرة

من الشائع في جافاسكربت تمديد قائمة العُقَد لتشمل عناصر في DOM. قد ترغب على سبيل المثال – بتمديد قائمة أسماء لتشمل ul تمّ استقبالها من الخادم باستخدام أجاكس. هذه إحدى الطرق المستخدمة لهذا الغرض:

window.onload = function() {
//ul element - <ul id="list"></ul>
var list = document.getElementById( 'list' );
 
var item = null;
 
//suppose this json is returned from ajax call
var ajaxResponse = [
   { 'name' : 'Haiku' },
   { 'name' : 'Linux' },
   { 'name' : 'OS X' },
   { 'name' : 'Windows' }
];
 
//add all names in ajaxReponse to documentFragment
for( var i in ajaxResponse ) {
   item = document.createElement( 'li' );
   item.appendChild( document.createTextNode( ajaxResponse[ i ].name ) );
   list.appendChild( item );
}
} //end onload
 
/*
..:: OUTPUT ::..
<ul id="list">
<li>Haiku</li>
<li>Linux</li>
<li>OS X</li>
<li>Windows</li>
</ul>
*/

المشكلة في هذه الطريقة أنّه في كلّ مرّة يتم فيها تمديد العنصر ليشمل حلقة “for in”، يتمّ تحديث DOM فوراً. هذا قد يؤثّر على الأداء، فالتلاعب بـ DOM عملية مكلفة!

DocumentFragment إصدار خفيف من المستند ليس له أثر في ما يظهر لمستخدم الصفحة.

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

window.onload = function() {
   //create DocumentFragment
   var documentFragment = document.createDocumentFragment();
 
   var list = document.getElementById( 'list' ); //<ul id="list"></ul>
   var item = null;
 
   //suppose this json is returned from ajax call
   var ajaxResponse = [
       { 'name' : 'Haiku' },
       { 'name' : 'Linux' },
       { 'name' : 'OS X' },
       { 'name' : 'Windows' }
   ];
 
   //add all names in ajaxReponse to documentFragment
   for( var i in ajaxResponse ) {
       item = document.createElement( 'li' );
       item.appendChild( document.createTextNode( ajaxResponse[ i ].name ) );
       documentFragment.appendChild( item );
   }
 
   //append all items in documentFragment to list
   list.appendChild( documentFragment );
}

كتب John Resig مقالاً ممتازاً يشرح فيه DocumentFragment وأثره على الأداء.

التلاعب بـDOM باستخدام innerHTML

لا تستخدم معاملات المهام الحسابيّة ( += ) مع innerHTML لإضافة وسم. كلّ مرّة تعدّل فيها innerHTML يتمّ تحديث DOM (يسحب المتصفّح الوسم). لذا فإنّ إضافة وسم جديد باستخدام معامل += يقلّل الأداء، خاصّة إذا كان هذا ضمن حلقة.

var container = document.getElementById( 'container' );
 
for( var i = 1; i <= 10; ++i ) {
   container.innerHTML += 'Item ' + i + '<br />';
}

دائماً استخدم متغيّراً مؤقّتاً لتسند إليه قيمة innerHTML كما في المثال أدناه:

var container = document.getElementById( 'container' )
 , str = '';
 
for( var i = 1; i <= 10; ++i ) {
   str += 'Item ' + i + '<br />';
}
 
container.innerHTML += str;