الدالة eval()‎ في JavaScript

من موسوعة حسوب

الدالة eval()‎ تُقدِّر قيمة شيفرة JavaScript المُمثَّلة في سلسلة نصية.

البنية العامة

eval(string)

string

سلسلة نصية تُمثِّل أحد التعبيرات أو التعبيرات البرمجية أو سلسلة من التعبيرات البرمجية في JavaScript، ويمكن أن يحتوي التعبير أيضًا على متغيرات وخاصيات لكائنات موجودة مسبقًا.

القيمة المعادة

ستُعيد الدالة eval()‎ قيمة آخر تعبير قد قُدِّرَت قيمته، وإذا كانت قيمته فارغةً فستُعاد القيمة undefined.

الوصف

الدالة eval()‎ هي خاصيةٌ للكائن العام (global object)، وتقبل وسيطًا هو سلسلة نصية، وإذا كانت هذه السلسلة النصية تُمثِّل تعبيرًا (expression أو statement) أو أكثر، فستُقدِّر الدالةُ eval()‎ قيمته؛ لا تستدعِ الدالة eval()‎ لتقدير قيمة التعابير الرياضية، إذ تقدِّر لغة JavaScript قيمة التعابير الرياضية تلقائيًا.

إذا ركّبتَ تعبيرًا رياضيًا كسلسلة نصية، فيمكنك استخدام الدالة eval()‎ لتقدير قيمته في وقتٍ لاحق، فلنفترض مثلًا أنَّ لديك المتغير x، فيمكنك تأجيل تقدير تعبير يتضمن المتغير x بإسناد ذلك التعبير الرياضي إلى متغير (ولتكن قيمة تلك السلسلة النصية هي "‎3 * x + 2") ومن ثم استدعاء الدالة eval()‎ في مرحلة لاحقة في البرنامج.

إذا لم يكن نوع الوسيط المُمرَّر إلى الدالة eval()‎ سلسلةً نصيةً فستعيد الدالة eval()‎ الوسيط كما هو، ففي المثال الآتي استخدمنا الدالة البانية String، وأعادت الدالة eval()‎ الكائن String بدلًا من تقدير قيمة السلسلة النصية:

eval(new String('2 + 2')); // String object containing "2 + 2"
eval('2 + 2');             // 4

يمكنك الالتفاف حول هذه المحدودية عبر استخدام الدالة toString()‎:

var expression = new String('2 + 2');
eval(expression.toString());

إذا استخدمتَ الدالة eval بشكلٍ غير مباشر، أي باستدعائها عبر مرجعية إليها بدلًا من استخدام eval مباشرة، فستُقدِّر الدالة eval قيمتها في المجال العام بدلًا من المجال المحلي وذلك بدءًا من إصدار ECMAScript 5.1. ما سبق يعني أنَّ تعابير التصريح عن الدوال ستؤدي إلى إنشاء دوال عامة والشيفرة التي ستُقدَّر قيمتها لن تملك وصولًا إلى المتغيرات المحلية في المجال المحلي الذي استدعاها. انظر مليًا إلى المثال الآتي لإزالة الغموض عن المثال السابق:

function test() {
  var x = 2, y = 4;
  console.log(eval('x + y'));  // استدعاء مباشر يستخدم المجال المحلي، ويعيد 6
  var geval = eval; // إسناد مرجعية للدالة إلى متغير
  console.log(geval('x + y')); // استدعاء غير مباشر يستخدم المجال العام، وسيرمي الخطأ ReferenceError because `x` is undefined
  (0, eval)('x + y'); // مثال آخر عن استدعاء غير مباشر
}

لا تستخدم eval دون حاجة

الدالة eval()‎ دالةٌ خطيرةٌ، التي ستُفِّذ الشيفرة التي ستُمرَّر إليها بنفس امتيازات البرنامج الذي استدعاها، فلو استدعيت الدالة eval()‎ مع سلسلة نصية التي يمكن تعديل قيمتها من طرفٍ له نوايا خبيثة، فسينتهي بك المطاف بتشغيل شيفرات مضرة على جهاز المستخدم بنفس امتيازات موقعك أو إضافتك؛ أضف إلى ذلك أنَّ الشيفرة الخبيثة يمكنها أن ترى ما في مجال استدعاء الدالة eval()‎، مما يؤدي إلى زيادة نطاق الهجوم...

لاحظ أنَّ استخدام الدالة eval()‎ يكون أبطأ من غيرها من البدائل، وذلك لأنها تؤدي إلى تشغيل مُفسِّر JavaScript، بينما البنى البرمجية الأخرى يمكن تحسين أداؤها من قِبل أغلبية محركات JavaScript الحديثة.

سنذكر فيما يلي من الأقسام البدائل الشائعة للدالة eval()‎ لمختلف الاستخدامات الشائعة.

الوصول إلى خاصيات الكائنات

لا تستخدم الدالة eval()‎ لمحاولة الوصول إلى خاصيات الكائنات، فخذ مثلًا المثال الآتي الذي لن نعرف فيه اسم الخاصية التي نريد الوصول إليها إلى أن تُنفَّذ الشيفرة. انظر إلى طريقة استخدام الدالة eval()‎:

var obj = { a: 20, b: 30 };
var propName = getPropName();  // تُعيد "a" أو "b"

eval( 'var result = obj.' + propName );

لكن لاحظ أنَّ الدالة eval()‎ غير ضرورية هنا، ولا ينصح باستخدامها هنا، وإنما علينا استخدام طرائق الوصول إلى الخاصيات، التي تكون أكثر أمانًا وسرعةً:

var obj = { a: 20, b: 30 };
var propName = getPropName();  // تُعيد "a" أو "b"
var result = obj[ propName ];  //  obj[ "a" ] = obj.a

يمكنك استخدام هذه الطريقة للوصول إلى الخاصيات الفرعية (التي تتبع لكائنٍ يتبع لكائنٍ آخر). انظر كيف سنستخدم الدالة eval()‎ هنا:

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // ستُعيد مثلًا "a.b.c"

eval( 'var result = obj.' + propPath );

يمكن تفادي استخدام الدالة eval()‎ بتقسيم «مسار» الخاصية والمرور على مختلف الخاصيات:

function getDescendantProp(obj, desc) {
  var arr = desc.split('.');
  while (arr.length) {
    obj = obj[arr.shift()];
  }
  return obj;
}

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // ستعيد مثلًا "a.b.c"
var result = getDescendantProp(obj, propPath);

يمكن استخدام الطريقة السابقة نفسها لضبط قيم الخاصيات:

function setDescendantProp(obj, desc, value) {
  var arr = desc.split('.');
  while (arr.length > 1) {
    obj = obj[arr.shift()];
  }
  obj[arr[0]] = value;
}

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // ستعيد مثلًا "a.b.c"
var result = setDescendantProp(obj, propPath, 1);  // test.a.b.c = 1

استخدام الدوال بدلًا من تقدير قيمة مقتطفات من الشيفرة

تملك JavaScript دوالًا من الصنف الأول (first-class functions)، وهذا يعني أنَّ بالإمكان تمرير الدوال كوسائط إلى دوال أخرى، أو تخزينها في متغيرات أو خاصيات الكائنات، وهلمَّ جرًا. وقد صُمِّمَت الواجهات البرمجية لشجرة DOM بأخذ ذلك بالحسبان، فيمكنك أن تضع:

setTimeout(function() { ... }, 1000);

بدلًا من:

setTimeout(" ... ", 1000)

أو أن تضع:

elt.addEventListener('click', function() { ... } , false);

بدلًا من:

elt.setAttribute("onclick", "...")

تحويل سلاسل JSON إلى كائنات JavaScript

إذا كانت السلسلة النصية التي مررتها إلى الدالة eval()‎ تحتوي على بيانات (مثلًا، المصفوفة ‎"‎‎[1, 2, 3]"‎) وليس شيفرات وتعابير برمجية، فمكن المستحسن التحويل إلى صيغة JSON، مما يسمح للسلسلة النصية استخدام قسم من بنية JavaScript لتمثيل البيانات.

أبقِ في ذهنك أنَّ صيغة JSON محدودة بالنسبة إلى صيغة JavaScript، فالكثير من الأشياء المسموحة في JavaScript لا يمكن استخدمها في JSON، فلا يمكن -على سبيل المثال- استخدام فاصلة زائدة في نهاية الكائن (trailing comma)، ويجب أن تكون أسماء الخاصيات (أي المفاتيح) موضوعةً بين علامتَي اقتباس.

أمثلة

استخدام eval

هنالك تعبيران برمجيان في المثال الآتي يستخدمان الدالة eval()‎ التي ستُعيد القيمة 42، وأوّل تعبير برمجي سيُقدِّر قيمة السلسلة النصية ‎"x + y + 1"‎ أما التعبير البرمجي الثاني فسيُقدِّر قيمة السلسلة النصية "42":

var x = 2;
var y = 39;
var z = '42';
eval('x + y + 1'); // 42
eval(z);           // 42

استخدام eval لتقدير قيمة التعابير البرمجية

سنستخدم الدالة eval()‎ في المثال الآتي لتقدير قيمة السلسلة النصية str، وهذه السلسلة النصية تحتوي على تعابير JavaScript التي تطبع قيمة المتغير z وتُسنِد القيمة 42 إليه إذا كانت قيمة المتغير x تساوي 5، وإلا فستُسنِد القيمة 0 إلى المتغير z:

var x = 5;
var str = "if (x == 5) {console.log('z is 42'); z = 42;} else z = 0;";

console.log('z is', eval(str)); // z is 42  z is 42

لاحظ أنَّ آخر قيمة من السلسلة النصية التي ستُمرَّر إلى الدالة eval()‎ هي القيمة التي ستُعاد:

var x = 5;
var str = "if (x == 5) {console.log('z is 42'); z = 42; x = 420; } else z = 0;"; 

console.log('x is', eval(str)); // z is 42  x is 420

ستُعاد قيمة آخر تعبير

الدالة eval()‎ ستُعيد قيمة آخر تعبير قُدِّرَت قيمته:

var str = 'if ( a ) { 1 + 1; } else { 1 + 2; }';
var a = true;
var b = eval(str);  // 2
 
console.log('b is', b);

a = false;
b = eval(str);  // 3

console.log('b is', b);

يجب إحاطة تعابير التصريح عن الدوال بقوسين

عند التصريح عن الدوال ضمن سلسلة نصية تُمرَّر إلى الدالة eval()‎ فيجب إحاطتها بقوسين هلاليين، كما في المثال الآتي، الذي ستُعاد فيه القيمة undefined عند عدم استخدام قوسين:

var fctStr1 = 'function a() {}'
var fctStr2 = '(function a() {})'
var fct1 = eval(fctStr1)  // undefined
var fct2 = eval(fctStr2)  // function

دعم المتصفحات

الميزة Chrome Firefox Internet Explorer Opera Safari
الدعم الأساسي نعم نعم نعم نعم نعم

مصادر ومواصفات