٨ يونيو ٢٠٢٠

معالجة الأخطاء, “try…catch”

بغض النظر عن مدى روعتنا في البرمجة ، في بعض الأحيان تحتوي أكوادنا على أخطاء. قد تحدث بسبب أخطائنا ، إدخال المستخدم معلومة غير متوقعة ، واستجابة خاطئة للخادم ، ولآلاف الأسباب الأخرى.

عادة “يتعطل” البرنامج في حالة حدوث خطأ (يتوقف فورًا) ، مع ظهور الخطأ في وحدة التحكم.

باستخدام “try… catch” “لالتقاط” الأخطاء ، يمكننا القيام بأشياء أكثر منطقية بدلاً من التوقف.

بناء جملة “try…catch”

تتكون بنية try..catch من كتلتين رئيسيتين: try, ثم catch:

try {

  // code...

} catch (err) {

  // error handling

}

يعمل كالتالي:

  1. أولا, يشتغل الكود الذي بداخل try {...}.
  2. إذا لم تكن هناك أخطاء, يتم تجاهل catch(err) و يعمل الكود إلا نهاية try , متجاوزا catch.
  3. في حالة حدوث خطأ, يتوقف الكود داخل try عن الإشتغال, و يبدأ عمل catch(err). المتغير err (يمكننا استخدام أي اسم له) سيحتوي على error object مع تفاصيل حول ما حدث.

إذا, لا يؤدي خطأ داخل try {…} إلى توقف البرنامج – لدينا فرصة للتعامل معه في catch.

دعونا نلقي نظرة على بعض الأمثلة.

  • مثال بدون أخطاء: يظهر alert (1) و alert (2):

    try {
    
      alert('بداية عمل try');  // (1) <--
    
      // ...لا توجد أخطاء هنا
    
      alert('نهاية عمل try');   // (2) <--
    
    } catch(err) {
    
      alert('بما أنه لا توجد أخطاء , تم تجاهل Catch'); // (3)
    
    }
  • مثال مع وجود خطأ: يظهر alert (1) و alert (3):

    try {
    
      alert('بداية عمل try');  // (1) <--
    
      lalala; // خطأ ، لم يتم تعريف المتغير!
    
      alert('نهاية عمل try (لن يشتغل)');  // (2)
    
    } catch(err) {
    
      alert(`حدث خطأ !`); // (3) <--
    
    }
try..catch تعمل فقط عند وجود أخطاء عند إشتغال البرنامج

لإشتغال try..catch , يجب أن يكون الكود قابلاً للتشغيل. بمعنى آخر ، يجب أن يكون جافا سكريبت صالحًا.

لن يعمل إذا كان الكود خاطئًا من الناحية النحوية ، على سبيل المثال يحتوي على أقواس و معقفات ناقصة:

try {
  {{{{{{{{{{{{
} catch(e) {
  alert("لا يستطيع المحرك فهم هذا الكود ، إنه غير صالح");
}

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

إذا, try..catch يمكن فقط معالجة الأخطاء التي تحدث في التعليمات البرمجية الصالحة. تسمى هذه الأخطاء “أخطاء وقت التشغيل” أو “استثناءات” في بعض الأحيان.

try..catch يعمل بشكل متزامن

إذا حدث استثناء في الكود “المجدول”, مثل setTimeout, إذا try..catch لن تشتغل:

try {
  setTimeout(function() {
    noSuchVariable; // سيتوقف البرنامج هنا
  }, 1000);
} catch (e) {
  alert( "لن تشتغل" );
}

ذلك لأن الدالة نفسها تُنفذ لاحقًا ، عندما يكون المحرك قد غادر بالفعل try..catch .

للحصول على استثناء داخل دالة مجدولة, try..catch يجب أن تكون داخل الدالة:

setTimeout(function() {
  try {
    noSuchVariable; // try..catch handles the error!
  } catch {
    alert( "error is caught here!" );
  }
}, 1000);

الكائن الخطأ

عند حدوث خطأ ، يقوم JavaScript بإنشاء كائن يحتوي على تفاصيل خاصة به. ثم يتم تمرير الكائن كوسيطة في catch:

try {
  // ...
} catch(err) { // <-- "err" الكائن الخطأ", يمكن استخدام كلمة أخرى بدلاً من
  // ...
}

بالنسبة لجميع الأخطاء المضمنة ، يحتوي هذا الكائن على خاصيتين رئيسيتين:

name
اسم الخطأ. على سبيل المثال ، لمتغير غير محدد ، هذا "ReferenceError".
message
رسالة نصية حول تفاصيل الخطأ.

هناك خصائص أخرى غير قياسية متاحة في معظم البيئات. واحدة من الأكثر استخدامًا ودعمًا هي:

stack
مكدس الاستدعاء الحالي: سلسلة تحتوي على معلومات حول تسلسل الإستدعاءات المتداخلة التي أدت إلى الخطأ. تستخدم لأغراض التصحيح.

على سبيل المثال:

try {
  lalala; // error, variable is not defined!
} catch(err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)

  // Can also show an error as a whole
  // The error is converted to string as "name: message"
  alert(err); // ReferenceError: lalala is not defined
}

“catch” دون متغير

إضافة حديثة
هذه إضافة حديثة إلى اللغه. المتصفحات القديمه ربما تتطلب polyfills.

إذا لم نكن بحاجة إلى تفاصيل الخطأ, catch يمكن أن تحثفه:

try {
  // ...
} catch { // <-- بدون (err)
  // ...
}

باستخدام “try…catch”

دعونا نستكشف حالة استخدام واقعية لي try..catch.

كما نعلم بالفعل, JavaScript تدعم JSON.parse(str) لقراءة قيمة JSON-encoded.

عادة يتم استخدامه لفك تشفير البيانات المستلمة عبر الشبكة ، من الخادم أو مصدر آخر.

نتلقى ذلك وندعو JSON.parse مثل التالي:

let json = '{"name":"John", "age": 30}'; // البيانات من الخادم

let user = JSON.parse(json); // تحويل النص إلى كائن JS

// الآن هو كائن له خصائص من النص user
alert( user.name ); // John
alert( user.age );  // 30

يمكنك العثور على معلومات أكثر تفصيلاً حول JSON في فصل الكائن json وكيفية استخدامه.

إذا json تالف, JSON.parse يولد خطأ, و “يتوقف” البرنامج.

هل يجب أن نكون راضين عن ذلك؟ بالطبع لا!

بهذه الطريقة ، إذا كان هناك خطأ ما في البيانات ، فلن يعرف الزائر مطلقًا ذلك (ما لم يفتح وحدة تحكم مطور البرامج). والناس لا يحبون حقا عندما يتوقف شيء ما دون أي رسالة خطأ.

لنستخدم try..catch للتعامل مع الخطأ:

let json = "{ bad json }";

try {

  let user = JSON.parse(json); // <-- عندما يحدث خطأ...
  alert( user.name ); // لا يعمل

} catch (e) {
  // ...the execution jumps here
  alert( "نعتذر ، البيانات بها أخطاء ، سنحاول أن نطلبها مرة أخرى." );
  alert( e.name );
  alert( e.message );
}

هنا نستخدم catch فقط لإظهار الرسالة, كن يمكننا القيام بالمزيد: إرسال طلب شبكة جديد ، اقتراح بديل للزائر ، إرسال معلومات حول الخطأ إلى منشأة تسجيل ، …. كل شيء أفضل من توقف البرنامج.

صناعة الأخطاء الخاصة

ماذا إذا json صحيح من الناحية اللغوية ، ولكن ليس له خاصية name ?

مثل هذا:

let json = '{ "age": 30 }'; // بيانات غير مكتملة

try {

  let user = JSON.parse(json); // <-- لا أخطاء
  alert( user.name ); // لا يوجد اسم !

} catch (e) {
  alert( "لا ينفذ" );
}

خنا يعمل JSON.parse بطريقة عادية, لكن غياب name في الواقع هو خطأ بالنسبة لنا.

التوحيد معالجة الأخطاء ، سنستخدم العامل throw.

العامل “Throw”

العامل throw يولد خطأ.

نقوم بكتابته كالتالي:

throw <error object>

من الناحية التقنية, يمكننا استخدام أي شيء ككائن خطأ. قد يكون حتى بدائيًا مثل رقم أو سلسلة ، ولكن من الأفضل أن يكون كائنًا ، ويفضل أن يكون مع خصائص name و message (للتوافق مع الأخطاء المضمنة).

يحتوي JavaScript على العديد من المنشئات المدمجة للأخطاء القياسية: Error, SyntaxError, ReferenceError, TypeError و اخرين. يمكننا استخدامها لإنشاء كائنات خطأ أيضًا.

نقوم بكتابتهم كالتالي:

let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...

للأخطاء المضمنة (ليس لأي كائنات ، للأخطاء فقط) ، خاصية name هي بالضبط اسم المنشئ. وتأخذ خاصية message من العوامل .

على سبيل المثال:

let error = new Error("حدث شيء ما o_O");

alert(error.name); // خطأ
alert(error.message); // حدث شيء ما o_O

دعونا نرى أي نوع من الأخطاء يولد JSON.parse:

try {
  JSON.parse("{ خاطئ json o_O }");
} catch(e) {
  alert(e.name); // SyntaxError
  alert(e.message); // Unexpected token b in JSON at position 2
}

كما نرى ، هذا هو SyntaxError.

وفي حالتنا غياب name خطأ ، حيث يجب أن يكون لدى المستخدمين name.

لذا دعونا نضف throw:

let json = '{ "age": 30 }'; // بيانات غير مكتملة

try {

  let user = JSON.parse(json); // <-- لا اخطاء

  if (!user.name) {
    throw new SyntaxError("بيانات غير مكتملة: no name"); // (*)
  }

  alert( user.name );

} catch(e) {
  alert( "JSON Error: " + e.message ); // JSON Error: بيانات غير مكتملة: no name
}

في السطر (*), throw يولد SyntaxError مع المعطى message, بنفس الطريقة التي تولدها JavaScript بنفسها. عمل try يتوقف على الفور و يتنقل إلى catch.

الان catch أصبح مكانًا واحدًا لكل معالجة الأخطاء: كلاهما لـ JSON.parse وحالات أخرى.

إعادة رمي الإستثناءات

في المثال أعلاه نستخدم try..catch للتعامل مع البيانات غير الصحيحة. ولكن هل من الممكن أن خطأ آخر غير متوقع يحدث داخل try {...} ؟ مثل خطأ في البرمجة (لم يتم تعريف المتغير) أو أي شيء آخر ، وليس فقط “البيانات غير الصحيحة” .

فمثلا:

let json = '{ "age": 30 }'; // بيانات غير مكتملة

try {
  user = JSON.parse(json); // <-- user قبل "let" نسيت أن تضع

  // ...
} catch(err) {
  alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
  // (no JSON Error actually)
}

بالطبع ، كل شيء ممكن! المبرمجون يرتكبون الأخطاء. حتى في المرافق المفتوحة المصدر التي يستخدمها الملايين لعقود – فجأة يمكن اكتشاف خطأ يؤدي إلى اختراق رهيب.

في حالتنا هذه, try..catch المقصود لالتقاط أخطاء “البيانات غير صحيحة”.ولكن بطبيعتها ، catch يحصل على كل الأخطاء من try. هنا يحصل على خطأ غير متوقع, ولكن لا يزال يظهر "JSON Error". هذا خطأ ويجعل أيضًا تصحيح التعليمات البرمجية أكثر صعوبة.القاعدة بسيطة:

يجب أن يقوم Catch بمعالجة الأخطاء التي يعرفها و “إعادة رمي” كل الآخرين فقط.

يمكن شرح تقنية “إعادة الرمي” بمزيد من التفصيل على النحو التالي:

  1. Catch يحصل على جميع الأخطاء.
  2. في catch(err) {...} نقوم بتحليل كائن الخطأ err.
  3. إذا لم نكن نعرف كيف نتعامل معها ، فنفعل throw err.

عادة ، يمكننا التحقق من نوع الخطأ باستخدام العامل instanceof:

try {
  user = { /*...*/ };
} catch(err) {
  if (err instanceof ReferenceError) {
    alert('ReferenceError'); // "ReferenceError" للوصول إلى متغير غير محدد

  }
}

يمكننا أيضًا الحصول على اسم فئة الخطأ من خاصية err.name. جميع الأخطاء الأصلية لديها هذه الخاصية. خيار آخر هو قراءة err.constructor.name.

في الكود أدناه ، نستخدم إعادة رمي بحيث catch يعالج فقط SyntaxError:

let json = '{ "age": 30 }'; // بيانات غير مكتملة
try {

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError("بيانات غير مكتملة: لا وجود لي name");
  }

  blabla(); // خطأ غير متوقع

  alert( user.name );

} catch(e) {

  if (e instanceof SyntaxError) {
    alert( "JSON Error: " + e.message );
  } else {
    throw e; // إعادة رمي (*)
  }

}

الخطأ في رمي السطر (*) من داخل catch “يسقط من” try..catch ويمكن إمساكه ببنية try..catch (إن وجدت), أو يتوقف البرنامج

إذا catch تعالج في الواقع الأخطاء التي تعرف كيف تتعامل معها و" تتخطى "جميع الآخرين.

يوضح المثال أدناه كيف يمكن اكتشاف مثل هذه الأخطاء بمستوى آخر من try..catch:

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // خطأ!
  } catch (e) {
    // ...
    if (!(e instanceof SyntaxError)) {
      throw e; // لا تعرف كيفية التعامل معه
    }
  }
}

try {
  readData();
} catch (e) {
  alert( "External catch got: " + e ); // قبض عليه!
}

هنا readData يعرف فقط كيفية التعامل مع SyntaxError, بينما خارج try..catch يعرف كيف يتعامل مع كل شيء.

try…catch…finally

انتظر ، هذا ليس كل شيء.

try..catch قد تحتوي على كود آخر: finally.

إذا كان موجودًا ، فإنه يعمل في جميع الحالات:

  • بعد try, إذا لم تكن هناك أخطاء ،
  • بعد catch, إذا كانت هناك أخطاء.

تبدو الصيغة الموسعة كما يلي:

try {
   ... حاول تشغيل الكود ...
} catch(e) {
   ... معالجة الأخطاء ...
} finally {
   ... يعمل في جميع الحالات ...
}

حاول تشغيل هذا الكود:

try {
  alert( 'حاول' );
  if (confirm('يوجد خطأ؟')) BAD_CODE();
} catch (e) {
  alert( 'إمسك' );
} finally {
  alert( 'أخيرا' );
}

يحتوي الكود على طريقتين للتنفيذ:

  1. إذا أجبت بـ “نعم” على “أخطأت؟”, ثم try -> catch -> finally.
  2. إذا قلت “لا”, ثم try -> finally.

غالبًا ما يتم استخدام عبارة finally عندما نبدأ في فعل شيء ونريد الانتهاء منه في أي حالة من النتائج.

على سبيل المثال ، نريد قياس الوقت الذي تستغرقه دالة أرقام فيبوناتشي fib(n). بطبيعة الحال ، يمكننا البدء في القياس قبل تشغيلها والانتهاء منها بعد ذلك. ولكن ماذا لو كان هناك خطأ أثناء استدعاء الدالة؟ على وجه الخصوص ، يؤدي تنفيذ fib(n) في الكود أدناه إلى إرجاع خطأ للأرقام السالبة أو غير الصحيحة.

عبارة finally هي مكان عظيم لإنهاء القياسات مهما كانت.

هنا finally يضمن أن الوقت سيتم قياسه بشكل صحيح في كلتا الحالتين – في حالة تنفيذ fib بنجاح وفي حالة حدوث خطأ فيه:

let num = +prompt("أدخل رقم صحيح موجب؟", 35)

let diff, result;

function fib(n) {
  if (n < 0 || Math.trunc(n) != n) {
    throw new Error("يجب ألا تكون سالبة ، وكذلك عددًا صحيحًا.");
  }
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
  result = fib(num);
} catch (e) {
  result = 0;
} finally {
  diff = Date.now() - start;
}

alert(result || "حدث خطأ");

alert( `${diff}ms مدة الإشتغال` );

يمكنك التحقق عن طريق تشغيل الكود بإدخال 35 في prompt – يتم تنفيذه بشكل طبيعي, finally بعد try. ثم أدخل -1 – سيكون هناك خطأ فوري ، وسيستغرق التنفيذ 0ms. تم إجراء القياسات بشكل صحيح.

بمعنى آخر ، قد تنتهي الدالة بـ return أو throw, وهذا لا يهم. تشتغل finally في كلتا الحالتين.

المتغيرات المحلية داخل try..catch..finally

يرجى ملاحظة أن متغيري result و diff في الكود أعلاه معلنين قبل try..catch.

وبخلاف ذلك ، إذا أعلنا عن let في try, فسوف تكون مرئية فقط داخلها.

finally و return

تعمل العبارة finally مع أي خروج من try..catch. وهذا يتضمن return.

في المثال أدناه ، هناك return في try. في هذه الحالة ، تعمل finally قبل العودة إلى الكود الخارجي مباشرةً.

function func() {

  try {
    return 1;

  } catch (e) {
    /* ... */
  } finally {
    alert( 'finally' );
  }
}

alert( func() ); // يعمل التنبيه أولاً من finally ، ثم هذا
try..finally

try..finally بدون عبارة catch, مفيد أيضًا. نطبقه عندما لا نريد معالجة الأخطاء هنا (دعهم يسقطون) ، ولكن نريد أن نتأكد من أن العمليات التي بدأناها قد انتهت.

function func() {
  // ابدأ في فعل شيء يحتاج إلى إكمال (مثل القياسات)
  try {
    // ...
  } finally {
    // أكمل هذا الشيء حتى لو توقف كل شيء
  }
}

في الكود أعلاه ، هناك خطأ داخل try دائمًا ما يقع ، لأنه لا يوجد catch. ولكن finally يعمل قبل أن يترك التنفيذ الدالة.

catch شاملة

Environment-specific

المعلومات الواردة في هذا القسم ليست جزءًا من أساسيات JavaScript.

لنفترض أننا حصلنا على خطأ فادح خارج try..catch, وتوقف البرنامج. مثل خطأ برمجة أو أي شيء فظيع آخر.

هل هناك طريقة للرد على مثل هذه الحوادث؟ قد نرغب في تسجيل الخطأ وإظهار شيء للمستخدم (عادةً لا يرون رسائل خطأ) ، إلخ.

لا يوجد شيء في المواصفات ، ولكن البيئات توفرها عادةً ، لأنها مفيدة حقًا. على سبيل المثال, Node.js له process.on("uncaughtException") for that. And in the من أجل هذا. وفي المتصفح يمكننا تعيين دالة للعامل الخاص window.onerror,التي سيتم تشغيلها في حالة وجود خطأ غير معلوم.

بناء الجملة:

window.onerror = function(message, url, line, col, error) {
  // ...
};
message
رسالة خطأ.
url
عنوان URL للنص البرمجي حيث حدث خطأ.
line, col
أرقام الأسطر والأعمدة حيث حدث الخطأ.
error
كائن خطأ.

على سبيل المثال:

<script>
  window.onerror = function(message, url, line, col, error) {
    alert(`${message}\n At ${line}:${col} of ${url}`);
  };

  function readData() {
    badFunc(); // عفوًا ، حدث خطأ ما!
  }

  readData();
</script>

دور المعالج الشامل window.onerror ادة لا يكون استرداد تنفيذ البرنامج النصي – ربما يكون ذلك مستحيلًا في حالة وجود أخطاء في البرمجة ، ولكن لإرسال رسالة الخطأ إلى المطورين.

هناك أيضًا خدمات الويب التي توفر تسجيل الأخطاء لمثل هذه الحالات ، مثل https://errorception.com أو http://www.muscula.com.

يعملون على هذا النحو:

  1. نسجل في الخدمة ونحصل على جزء JS (أو عنوان URL ) منها لإدراجها في الصفحات.
  2. يحدد هذا النص البرمجي لـ JS دالة window.onerror.
  3. عند حدوث خطأ ، يرسل طلب شبكة حوله إلى الخدمة.
  4. يمكننا تسجيل الدخول إلى واجهة ويب الخدمة ونرى الأخطاء.

الخلاصة

تسمح تركيبة try..catch بمعالجة أخطاء وقت التشغيل. يسمح حرفيا بـ “تجربة” تشغيل التعليمات البرمجية و “التقاط” الأخطاء التي قد تحدث فيه.

الصيغة هي:

try {
  // شغل هذا الكود
} catch(err) {
  // إذا حدث خطأ , واصل هنا
  // err هو كائن الخطأ
} finally {
  // تفعل في أي حال بعد try/catch
}

قد لا يكون هناك قسم catch أو لا يوجد finally, لذا فإن التركيبات الأقصر try..catch و try..finally صالحة أيضًا.

كائنات الخطأ لها الخصائص التالية:

  • message – رسالة خطأ يمكن قراءتها و فهمها.
  • name – اسم الخطأ (اسم مُنشئ الخطأ).
  • stack (غير قياسي ، ولكنه مدعوم جيدًا) – المجموعة في لحظة إنشاء الخطأ.

إذا لم تكن هناك حاجة إلى كائن خطأ ، يمكننا حذفه باستخدام catch بدلاً من catch(err).

يمكننا أيضًا إنشاء الأخطاء الخاصة بنا باستخدام عامل throw. من الناحية التقنية ، يمكن أن يكون محتوى throw متكون من أي شيء, ولكنها عادة ما تكون كائن خطأ يرث من فئة Error. المزيد عن توسيع الأخطاء في الفصل التالي.

إعادة الرمي هو نمط مهم جدًا في معالجة الأخطاء: عادة ما تتوقع catch وتعرف كيفية التعامل مع نوع الخطأ المعين, لذلك يجب أن تقوم بإعادة رمي الأخطاء التي لا تعرفها.

حتى إذا لم يكن لدينا try..catch, تسمح لنا معظم البيئات بإعداد معالج أخطاء “عام” للقبض على الأخطاء “التي تقع”. في المتصفح ، هذا هو window.onerror.

مهمه

الأهمية: 5

قارن بين جزئي الكود.

  1. يستخدم الأول finally لتشغيل الكود بعد try..catch:

    try {
      إعمل إعمل
    } catch (e) {
      معالجة الأخطاء
    } finally {
      تنظيف مكان العمل
    }
  2. الجزء الثاني يضع التنظيف مباشرة بعد try..catch:

    try {
      إعمل إعمل
    } catch (e) {
      معالجة الأخطاء
    }
    
    تنظيف مكان العمل

نحن بالتأكيد بحاجة إلى التنظيف بعد العمل ، لا يهم إذا كان هناك خطأ أم لا.

هل هناك ميزة في استخدام finally أم أن جزئي الكود متساويان؟ إذا كان هناك مثل هذه الميزة ، فقم بإعطاء مثال عندما يكزن مهم.

يصبح الفرق واضحًا عندما ننظر إلى الكود داخل دالة.

يختلف السلوك إذا كان هناك “خروج” من try..catch.

على سبيل المثال ، عندما يكون هناك return داخل try..catch. يعمل finally في حالة أي خروج من try..catch, حتى عبر عبارة return: مباشرة بعد الانتهاء من try..catch, ولكن قبل أن يحصل الكود الذي قمنا بالاتصال به على التحكم.

function f() {
  try {
    alert('إبدء');
    return "النتيجة";
  } catch (e) {
    /// ...
  } finally {
    alert('نظف!');
  }
}

f(); // نظف!

…أو عندما يكون هناك throw, مثلما هو الحال هنا:

function f() {
  try {
    alert('إبدء');
    throw new Error("an error");
  } catch (e) {
    // ...
    if("can't handle the error") {
      throw e;
    }

  } finally {
    alert('نظف!')
  }
}

f(); // نظف!

يضمن finally التنظيف. إذا وضعنا الكود في نهاية f, فلن يتم تشغيلها في هذه المواقف.

خريطة الدورة التعليمية

التعليقات

إقرأ هذا قبل أن تضع تعليقًا…
  • إذا كان لديك اقتراحات أو تريد تحسينًا - من فضلك من فضلك إفتح موضوعًا فى جيتهاب أو شارك بنفسك بدلًا من التعليقات.
  • إذا لم تستطع أن تفهم شيئّا فى المقال - وضّح ماهو.
  • إذا كنت تريد عرض كود استخدم عنصر <code> ، وللكثير من السطور استخدم <pre>، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)