مسار التحكّم

مسار التحكم هو الترتيب الذي ينفّذ به مترجم JavaScript العبارات. إذا كان النص لا يتضمن عبارات تغير تدفقه، فسيتم تنفيذه من البداية إلى النهاية، سطرًا واحدًا في كل مرة. تُستخدم بُنى التحكّم لتحديد ما إذا كان يتم تنفيذ مجموعة من العبارات استنادًا إلى مجموعة محدّدة من المعايير أو تنفيذ مجموعة من العبارات بشكل متكرر أو مقاطعة تسلسل العبارات.

العبارات الشرطية

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

ifelse

تقيّم عبارة if شرطًا داخل الأقواس المطابقة التالية. إذا تم تقييم الشرط داخل الأقواس إلى true، يتم تنفيذ العبارة أو عبارة الحظر التي تلي الأقواس المتطابقة:

if ( true ) console.log( "True." );
> "True."

if ( true ) {
    const myString = "True.";
    console.log( myString );
}
> "True."

إذا تم تقييم الشرط داخل الأقواس بقيمة false، يتم تجاهل العبارة التي تليه:

if ( false ) console.log( "True." );

وتحدّد الكلمة الرئيسية else التي تلي عبارة if مباشرةً وعبارتها التي تم تنفيذها مشروطًا العبارة المطلوب تنفيذها إذا تم تقييم شرط if إلى false:

if ( false ) console.log( "True." )''
else console.log( "False" );
> "False."

لتجميع عبارات if متعددة معًا، يمكنك جعل العبارة التي يتم تنفيذها مشروطًا بعد else عبارة if أخرى:

const myCondition = 2;
if ( myCondition === 5 ) console.log( "Five." );
else if ( myCondition === 2 ) console.log( "Two." );

ننصحك بشدة باستخدام بنية عبارة الكتلة التالية للعبارات الشرطية لتحسين إمكانية القراءة، ولكن غالبًا ما تكون عبارات else if استثناءً لذلك:

if ( myCondition === 5 ) {
    console.log( "Five." );
} else if ( myCondition === 3 ) {
    console.log( "Three" );
} else {
    console.log( "Neither five nor three." );
}
> "Neither five nor three."

عامل تشغيل ثلاثي

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

  • تمثّل هذه السمة شرطًا يتم تقييمه تليه علامة استفهام (?).
  • التعبير المطلوب تنفيذه إذا تم تقييم الشرط إلى true، متبوعة بنقطتَين (:).
  • التعبير المطلوب تنفيذه إذا تم تقييم الشرط إلى false.

غالبًا ما يتم استخدام هذا لضبط قيمة أو تمريرها بشكل مشروط:

const myFirstResult  = true  ? "First value." : "Second value.";
const mySecondResult = false ? "First value." : "Second value.";

myFirstResult;
> "First value."

mySecondResult;
> "Second value."

switchcase

استخدِم عبارة switch لمقارنة قيمة تعبير معيّن بقائمة من القيم المحتملة المحدّدة باستخدام كلمة رئيسية واحدة أو أكثر من كلمات case. بناء الجملة هذا غير معتاد لأنه يأتي من بعض قرارات التصميم الأولى لـ JavaScript. switch...case يستخدم الكلمة الرئيسية switch، متبوعة بتعبير ليتم تقييمه بشكل ملفوف بين قوسين، متبوعًا بزوج متطابق من الأقواس المعقوفة. يمكن أن يحتوي نص switch على كلمات رئيسية case، عادةً واحدة أو أكثر، متبوعة بتعبير أو قيمة، متبوعة بعلامة النقطتين (:).

عندما يصل المترجم الفوري إلى case مع قيمة تطابق التعبير الذي يتم تقييمه بين القوسين بعد الكلمة الرئيسية switch، ينفّذ أي عبارات تلي عبارة case هذه:

switch ( 2 + 2 === 4 ) {
  case false:
    console.log( "False." );
  case true:
    console.log( "True." );
}
> "True."

يتم تنفيذ جميع العبارات التي تلي علامة case المطابقة، حتى إذا كانت مضمَّنة في عبارة حظر.

switch ( 2 + 2 === 4 ) {
    case false:
    console.log( "False." );
  case true:
    let myVariable = "True.";
    console.log( myVariable );

}
> "True."

إحدى مخاطر استخدام switch…case هي أنه بعد العثور على تطابق، ينفذ مترجم JavaScript أي عبارة تتبع case المتطابق، حتى تلك الواردة ضمن عبارات case الأخرى. يسمى ذلك "الانتقال" إلى case التالي:

switch ( 2 + 2 === 7 ) {
    case false:
    console.log( "False." );
  case true:
    console.log( "True." );
}
> "False."
> "True."

لتجنُّب أي أخطاء، عليك إنهاء كل حالة باستخدام الكلمة الرئيسية break، ما يؤدي إلى إيقاف تقييم نص switch فورًا:

switch ( 2 + 2 === 7 ) {
    case false:
    console.log( "False." );
    break;
  case true:
    console.log( "True." );
    break;
}
> "False."

إذا لم تتطابق أي دالة case مع القيمة الشرطية، تختار switch عبارة default إذا كانت هناك واحدة:

switch ( 20 ) {
    case 5:
    console.log( "The value was five." );
    break;
  case 10:
    console.log( "The value was ten." );
    break;
  default:
    console.log( "The value was something unexpected." );
}
> "The value was something unexpected."

في المقابل، ينطبق هذا الانخفاض على default أيضًا، ما قد يؤدي إلى نتائج غير متوقّعة. لإصلاح هذه المشكلة، يمكنك إنهاء عبارة default بكلمة break، أو وضعها في آخر بيان في قائمة الحالات.

switch ( 20 ) {
  default:
    console.log( "The value was something unexpected." );
  case 10:
    console.log( "The value was ten." );
    break;
  case 5:
    console.log( "The value was five." );
    break;
}
> The value was something unexpected.
> The value was ten.

بما أنّ عبارات case لا تتطلّب عبارة حظر لتجميع عبارات متعددة، لا تنشئ عبارتي case وdefault نطاقًا لغويًا بذاتهما:

let myVariable;
switch ( true ) {
  case true:
    let myVariable = "True.";
    break;
  default:
    let myVariable = "False.";
    break;
}
> Uncaught SyntaxError: redeclaration of let myVariable

لإدارة النطاق، يمكنك استخدام بيانات الحظر:

let myVariable;
switch ( true ) {
  case true: {
    let myVariable = "True.";
    break;
  }
  default: {
    let myVariable = "False.";
    break;
  }
}

التكرارات الحلقية والتكرار التحسيني

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

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

let iterationCount = 0;
console.log( "Pre-loop." );
while( iterationCount < 3 ) {
  iterationCount++;
  console.log( "Loop iteration." );
}
console.log( "Continuing on." );
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Continuing on."

وإذا تعذّر استيفاء الشروط أثناء تنفيذ التكرار الحلقي، يستمر التكرار إلى أجل غير مسمى. تمثّل هذه التكرارات اللانهائية مشكلة شائعة في البرمجة قد تؤدي إلى توقُّف سلسلة التنفيذ الرئيسية إلى أجل غير مسمى أو قد تتسبّب في تعطُّل علامة تبويب في المتصفِّح.

يتم تنفيذ المثال التالي طالما أنّ القيمة المنطقية true لا تزال true. لأنّ القيم المنطقية غير قابلة للتغيير، يؤدي ذلك إلى إنشاء حلقة لانهائية.

console.log( "Pre-loop." );
while( true ) {
console.log( "Loop iteration." );
}
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
…

تجنَّب ترك حلقات لانهائية في رمز الإنتاج. إذا قمت بإنشاء تحديث عن طريق الخطأ أثناء التطوير، يمكنك إصلاحه عن طريق إغلاق علامة تبويب المتصفح قيد التشغيل، وتحديث التعليمة البرمجية بحيث لا يعد التكرار إلى ما لا نهاية، ثم إعادة فتح الصفحة.

while

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

let iterationCount = 0;
while( iterationCount < 3 ) {
  iterationCount++;
  console.log( `Loop ${ iterationCount }.` );
}
> "Loop 1."
> "Loop 2."

إذا عثر المترجَم على عبارة continue في حلقة while، يتم إيقاف هذا التكرار وإعادة تقييم الشرط ويواصل التكرار الحلقي إن أمكن:

let iterationCount = 0;
while( iterationCount <= 5 ) {
  iterationCount++;
  if( iterationCount === 3 ) {
    continue;
  }
  console.log( `Loop ${ iterationCount }.` );
}
console.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Loop 4."
> "Loop 5."
> "Loop ended."

إذا عثر المترجَم على عبارة break في حلقة while، هذا يعني أنّ التكرار يتوقف ولا تتم إعادة تقييم الشرط، ما يسمح للمترجم بالمضي قدمًا:

let iterationCount = 1;
while( iterationCount <= 5 ) {
  if( iterationCount === 3 ) {
    console.log(`Iteration skipped.``);`
    break;
  }
  console.log( `Loop ${ iterationCount }.` );
  iterationCount++;
}
console.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Iteration skipped.
> "Loop ended."

يمكنك استخدام while لتكرار عدد محدّد من المرّات، كما هو موضّح في المثال السابق، إلا أنّ حالة الاستخدام الأكثر شيوعًا للسمة while هي تكرار حلقي في عدد غير محدَّد من المرّات:

let randomize = () => Math.floor( Math.random() * 10 );
let randomNum = randomize();
while( randomNum !== 3 ){
  console.log( `The number is not ${ randomNum }.` );
  randomNum = randomize();
}
console.log( `The correct number, ${ randomNum }, was found.` );
> "The number is not 0."
> "The number is not 6."
> "The number is not 1."
> "The number is not 8."
> "The correct number, 3, was found."

dowhile

do...while هو أحد صيغ التكرار الحلقي while الذي يحدث فيه التقييم الشرطي في نهاية كل تكرار في التكرار الحلقي. هذا يعني أن نص التكرار يتم تنفيذه دائمًا مرة واحدة على الأقل.

لإنشاء حلقة تكرار do...while، استخدِم الكلمة الرئيسية do متبوعة بالعبارة (أو عبارة الحظر) ليتم تنفيذها عند كل تكرار للتكرار. بعد هذه العبارة مباشرةً، أضِف while وأقواسًا مطابِقة تحتوي على الشرط المطلوب تقييمه. وعند انتهاء تقييم هذا الشرط إلى true، ينتهي حلقة التكرار.

let iterationCount = 1;
do {
  console.log( `Loop ${ iterationCount }.` );
  iterationCount++;
} while ( iterationCount < 3 );
> "Loop 1."
> "Loop 2."
> "Loop 3."

كما هي الحال في التكرار الحلقي while، إنّ حالة الاستخدام الأكثر شيوعًا لـ do...while هي حلقة ذات طول غير محدّد:

let randomNum;
do {
  randomNum = ( () => Math.floor( Math.random() * 10 ) )();
  console.log( `Is the number ${ randomNum }?` );
} while ( randomNum !== 3 );
console.log( `Yes, ${ randomNum } was the correct number.` );
> "Is the number 9?"
> "Is the number 2?"
> "Is the number 8?"
> "Is the number 2?"
> "Is the number 3?"
> "Yes, 3 was the correct number."

for

استخدِم تكرارات for لتكرار كمية معروفة. في قواعد التعليمات البرمجية القديمة، كان يستخدم هذا بشكل متكرر للتكرار التحسيني على العناصر في صفيف.

لإنشاء حلقة تكرار for، استخدِم الكلمة الرئيسية for متبوعة بمجموعة من الأقواس تقبل التعبيرات الثلاثة التالية بالترتيب ومفصولة بحروف صغيرة:

  1. تعبير سيتم تقييمه عند بدء التكرار الحلقي
  2. يشير هذا المصطلح إلى شرط يحدّد ما إذا كان يجب مواصلة التكرار.
  3. تعبير يتم تنفيذه في نهاية كل تكرار

بعد هذين القوسين، أضِف العبارة (عادةً عبارة الحظر) ليتم تنفيذها أثناء التكرار الحلقي.

for( let i = 0; i < 3; i++ ) {
  console.log( "This loop will run three times.")
}

يقوم التعبير الأول بتهيئة متغير يعمل كعداد. يتم تقييم هذا التعبير مرة واحدة، قبل التكرار الأول للتكرار الحلقي. يمكنك إعداد هذا المتغيّر باستخدام let (أو var في السابق) مثل أي متغير آخر، ويكون نطاقه هو نص التكرار الحلقي. وقد يكون لهذه المتغيرات أي معرّف صالح، ولكن تسمّى غالبًا i بمعنى "التكرار" أو "الفهرس". يبدو أنّ هذا الإجراء يتعارض مع أفضل الممارسات الراسخة لأسماء المعرّفات التي يمكن توقّعها، إلا أنّ الاصطلاح قد تم تأسيسه بما يكفي ليكون واضحًا للمطوّرين الآخرين بنظرة سريعة. نظرًا إلى عدم فهرسة المجموعات المفهرَسة، تكون قيمة هذه المتغيرات في بعض الأحيان مبدئية تبلغ 0.

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

يتم تنفيذ التعبير النهائي في نهاية كل تكرار من خلال التكرار الحلقي. وعادةً ما يتم استخدامه لزيادة المعرّف بمقدار واحد.

غالبًا ما تلاحظ تكرار حلقات for في الصفائف في قواعد الرموز القديمة. في هذه الحالات، يكون الشرط المحدد لمتابعة التكرار الحلقي هو عدد التكرار أقل من أو يساوي طول الصفيف الذي يتم تكراره من خلاله. يُستخدم المتغير المستخدم لتتبع عدد التكرار الحالي للبحث عن القيمة المرتبطة بهذا الفهرس في الصفيف، مما يسمح بالتعامل مع كل عنصر في الصفيفة بالترتيب:

var myArray = [ true, false, true ];
for( let i = 0; i <= myArray.length; i++ ) {
  console.log( myArray[ i ] );
}
> true
> false
> true

لم يعُد هذا النهج مستخدَمًا لصالح الأساليب الأكثر حداثة لتكرار هياكل البيانات القابلة للتكرار.

for [...] of [...]

استخدِم التكرارات الحلقية for...of... لتكرار القيم المخزَّنة في بنية بيانات قابلة للتكرار، مثل صفيف أو مجموعة أو خريطة.

يستخدم التكرار الحلقي for...of... الكلمة الرئيسية for متبوعةً بمجموعة من الأقواس التي تحتوي على متغير، تليها of، ثم يتم تكرار بنية البيانات. يمكن أن يكون المتغيّر تعريفًا يتم تنفيذه هنا باستخدام let أو const أو var، أو متغيّر تم تعريفه سابقًا ضمن النطاق الحالي، أو خاصية عنصر، أو مثيل لإتلاف عملية التخصيص. وهي تحتوي على قيمة العنصر التي تتوافق مع التكرار الحالي للتكرار الحلقي.

const myIterable = [ true, false, true ];
for( const myElement of myIterable ) {
  console.log( myElement );
}
> true
> false
> true

في هذا المثال، يُجدي استخدام const مع myElement على الرغم من أنّ myElement يُمنح قيمة جديدة في كل تكرار في التكرار الحلقي. وذلك لأنّ المتغيرات التي تم تعريفها باستخدام let أو const يتم تحديدها على بيان الحظر ضمن الحلقة. تتم تهيئة المتغير في بداية كل تكرار، وتتم إزالته في نهاية هذا التكرار.

forin

استخدِم التكرارات for...in... للتكرار في الخصائص القابلة للتعداد للكائن، بما في ذلك الخصائص الموروثة التي يمكن تعدادها. كما هو الحال في التكرار الحلقي for...of...، يستخدم التكرار الحلقي for...in... الكلمة الرئيسية for متبوعة بمجموعة من الأقواس التي تحتوي على متغيّر يحتوي على قيمة مفتاح الخاصية المقابلة للتكرار الحالي للتكرار الحلقي. تليه الكلمة الرئيسية in، ثم العنصر الذي يتم تكراره على:

const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
  console.log( myKey );
}
> "myProperty"
> "mySecondProperty"

ومرة أخرى، على الرغم من تغيّر قيمة myKey مع كل تكرار في التكرار الحلقي، يمكنك استخدام const بدون خطأ لأنّه يتم تجاهل المتغيّر بشكل فعّال في نهاية كل تكرار، ثم إعادة إنشائه في البداية.

لا تتوفّر القيمة المرتبطة بكل مفتاح خاصية مباشرةً للبنية for...in.... ومع ذلك، نظرًا لأن التكرار الحلقي لديه مفتاح خاصية في كل تكرار، يمكنك استخدام هذا المفتاح "للبحث عن" قيمته:

const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
  const myValue = myObject[ myKey ];
  console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "mySecondProperty : false"

المواقع المكتسبة من دوال الإنشاء المضمنة هي مواقع غير قابلة للعد، وهذا يعني أن for...in... لا تتكرر من خلال الخصائص المكتسبة من الدالة الإنشائية Object. ومع ذلك، يتم تضمين أي خصائص قابلة للتعداد ضمن سلسلة النموذج الأولي للكائن:

const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
    myProperty: {
    value: true,
    enumerable: true
    }
});
for ( const myKey in myObject ) {
  const myValue = myObject[ myKey ];
  console.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "protoProperty : true"

توفر لغة JavaScript طرقًا مضمنة لتحديد ما إذا كانت الخاصية خاصية مباشرة للكائن بدلاً من خاصية في سلسلة النموذج الأوّلي للكائن: الطرق المحدّثة Object.hasOwn() والقديمة Object.prototype.hasOwnProperty(). تقيّم هذه الطرق ما إذا كانت سمة محدّدة مكتسبة (أو لم يتم تعريفها)، وتعرض true للخصائص المباشرة لكائن محدّد فقط:

const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
    myProperty: {
    value: true,
    enumerable: true
    }
});
for ( const myKey in myObject ) {
  const myValue = myObject[ myKey ];
  if ( Object.hasOwn( myObject, myKey ) ) {
    console.log( `${ myKey } : ${ myValue }` );
  }
}
> "myProperty : true"

هناك أيضًا ثلاث طرق ثابتة تعرض كل منها صفيفًا يتكون من مفاتيح تعداد الكائن (Object.keys()) أو القيم (Object.values()) أو أزواج المفتاح/القيمة (Object.entries()):

const myObject = { "myProperty" : true, "mySecondProperty" : false };
Object.keys( myObject );
> Array [ "myProperty", "mySecondProperty" ]

ويتيح لك هذا تكرار مفاتيح العناصر أو القيم أو أزواج المفتاح/القيمة (باستخدام تدمير التعيين) بدون تضمين الخصائص التي يملكها النموذج الأولي لهذا الكائن:

const myPrototype = { "protoProperty" : "Non-enumerable property value." };
const myObject = Object.create( myPrototype, {
    myProperty: {
    value: "Enumerable property value.",
    enumerable: true
    }
});

for ( const propKey of Object.keys( myObject ) ) {
  console.log( propKey );
}
> "myProperty"

for ( const propValue of Object.values( myObject ) ) {
  console.log( propValue );
}
> "Enumerable property value."

for ( const [ propKey, propValue ] of Object.entries( myObject ) ) {
  console.log( `${ propKey } : ${ propValue }` );
}
> "myProperty : Enumerable property value."

forEach()

توفر طرق forEach() التي تقدمها دوال إنشاء Array وMap وSet وNodeList اختصارًا مفيدًا للتكرار على هيكل البيانات في سياق دالة استدعاء. على عكس الأشكال الأخرى من التكرار الحلقي، لا يمكن مقاطعة الحلقة التي يتم إنشاؤها باستخدام أي طريقة forEach() باستخدام break أو continue.

forEach هي طريقة يمتلكها النموذج الأولي لكل بنية بيانات. تتوقع كل طريقة forEach دالة استدعاء كوسيطة، على الرغم من أنها تختلف قليلاً من حيث الوسيطات المضمنة عند استدعاء هذه الدالة. وتحدِّد الوسيطة الثانية الاختيارية قيمة this لاستخدامها كسياق استدعاء لدالة استرداد الاتصال.

توفّر دالة رد الاتصال المستخدمة مع Array.forEach معلَمات تحتوي على قيمة العنصر الحالي وفهرس العنصر الحالي والصفيف الذي تم استدعاء طريقة forEach عليه:

const myArray = [ true, false ];
myArray.forEach( ( myElement, i, originalArray ) => {
  console.log( i, myElement, originalArray  );
});
> 0 true Array(3) [ true, false ]
> 1 false Array(3) [ true, false ]

توفّر دالة رد الاتصال المستخدمة مع Map.forEach معلَمات تحتوي على القيمة المرتبطة بالعنصر الحالي، والمفتاح المرتبط بالعنصر الحالي، وطريقة "الخريطة" التي تم استدعاء طريقة forEach فيها:

const myMap = new Map([
  ['myKey', true],
  ['mySecondKey', false ],
]);
myMap.forEach( ( myValue, myKey, originalMap ) => {
    console.log( myValue, myKey, originalMap  );
});
> true "myKey" Map { myKey → true, mySecondKey → false }
> false "mySecondKey" Map { myKey → true, mySecondKey → false }

يحتوي رد الاتصال Set.forEach على معلَمات مشابهة. بما أنّ "Set" لا تتضمّن فهارس أو مفاتيح مميزة عن القيم، توفّر الوسيطة الثانية قيمة زائدة يمكن تجاهلها، وذلك للحفاظ على توافق البنية مع طرق forEach الأخرى.

const mySet = new Set([ true, false ]);
mySet.forEach( ( myValue, myKey, originalSet ) => {
  console.log( myValue, myKey, originalSet  );
});
> true true Set [ true, false ]
> false false Set [ true, false ]

المكررات

القابلة للتكرار هي أي بنية بيانات مكونة من عناصر فردية يمكن تكرارها باستخدام الأساليب التي تم توضيحها بالتفصيل سابقًا. المكرّر هو كائن قابل للتكرار ويتّبع بروتوكول المكرّر، ما يعني أنّه يجب تنفيذ طريقة next() التي تنتقل عبر العناصر التي تحتوي على عنصر واحد في كل مرة، وفي كل مرة يتم فيها استدعاء هذه الطريقة، وتعرض كائنًا لكل عنصر متسلسل بتنسيق معيّن.

بُنى البيانات القابلة للتكرار المضمَّنة في JavaScript (مثل صفيف وخريطة وتعيين) ليست مكرّرة في حد ذاتها، ولكنّها تكتسب كلّها طريقة iterator، ويمكن الوصول إليها باستخدام عنصر @@iterator well-known Symbol، الذي يعرض كائن تكرارًا تم إنشاؤه من بنية البيانات القابلة للتكرار:

const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();

myIterable;
> (3) [1, 2, 3]

myIterator;
> Array Iterator {}

عند استدعاء الإجراء next() في المكرّر يتخطّى العناصر التي يحتوي عليها واحدًا تلو الآخر، ويعرض كل طلب عنصرًا يحتوي على خاصيتَين: value، يحتوي على قيمة العنصر الحالي، وdone، وهي قيمة منطقية تخبرنا بما إذا كان المكرّر قد اجتاز العنصر الأخير في بنية البيانات. قيمة done هي true فقط عندما ينتج عن الاستدعاء إلى next() محاولة الوصول إلى عنصر خارج العنصر الأخير في المكرّر.

const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();

myIterator.next();
> Object { value: 1, done: false }

myIterator.next();
> Object { value: 2, done: false }

myIterator.next();
> Object { value: 3, done: false }

myIterator.next();
> Object { value: undefined, done: true }

وظائف المنشئ

استخدِم الكلمة الرئيسية function* (لاحظ علامة النجمة) للإعلان عن دالة المنشئ أو تحديد تعبير دالة المنشئ:

function* myGeneratorFunction() { };

تحتفظ دوال الإنشاء بحالتها، تمامًا مثل المكررات. يؤدي استدعاء دالة المنشئ إلى عرض كائن Generator جديد ولكنه لا ينفّذ على الفور الرمز في نص الدالة:

function* myGeneratorFunction() {
  console.log( "Generator function body ")
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject;
> Generator {  }

typeof myGeneratorObject;
> "object"

تتبع كائنات المنشئ بروتوكول المكرّر. يتمّ تحديد القيمة التي تعرضها كل استدعاء للسمة next() في دالة المنشئ من خلال التعبير yield، الذي يوقف تنفيذ دالة المنشئ مؤقتًا ويعرض قيمة التعبير الذي يحتوي على الكلمة الرئيسية yield. تؤدي طلبات البيانات اللاحقة إلى "next()" إلى مواصلة تنفيذ الدالة، مع الإيقاف المؤقت عند التعبير yield التالي، وعرض القيمة المرتبطة بها.

function* myGeneratorFunction() {
  yield "My first yielded value.";
  yield "My second yielded value.";
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject.next();
> Object { value: "My first yielded value.", done: false }

myGeneratorObject.next();
> Object { value: "My second yielded value.", done: false }

عند استدعاء next() بعد عدم تحديد أي قيم إضافية باستخدام yield أو return أو throw (في حال حدوث خطأ)، يتم تنفيذ باقي الدالة، ويكون للكائن الذي يتم عرضه value من undefined وخاصية done بـ true:


function* myGeneratorFunction() {
    console.log( "Start of the generator function." );
    yield "First";
    console.log( "Second part of the generator function."  );
    yield "Second";
    console.log( "Third part of the generator function." );
    yield "Third";
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject.next();
> "Start of the generator function."
> Object { value: "First", done: false }

myGeneratorObject.next();
> "Second part of the generator function."
> Object { value: "Second", done: false }

myGeneratorObject.next();
> "Third part of the generator function."
> Object { value: "Third", done: false }

myGeneratorObject.next();
> Object { value: undefined, done: true }

استخدِم next() فقط على العنصر الذي تعرضه دالة المنشئ، وليس مع دالة المنشئ نفسها. بخلاف ذلك، يؤدي كل استدعاء لدالة المنشئ إلى إنشاء كائن منشئ جديد:

function* myGeneratorFunction() {
  yield "First";
  yield "Second";
};

myGeneratorFunction().next();
> Object { value: "First", done: false }

myGeneratorFunction().next();
> Object { value: "First", done: false }

كما هو الحال مع أي دالة، تتوقف دالة المنشئ عندما تصادف كلمة رئيسية return. ثم تعرض كائنًا لسياق الاستدعاء الذي يحتوي على القيمة التي تم إرجاعها وسمة done بالقيمة true.

function* myGeneratorFunction() {
  yield 1;
  yield 2;
  return 3;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject.next().done;
> Object { value: 1, done: false }

myGeneratorObject.next().done;
> Object { value: 2, done: false }

myGeneratorObject.next();
> Object { value: 3, done: true }

يمكن أن يحمل تعبير yield بعض دلالات المعرّف، ما يسمح "بالتواصل" الثنائي من الجزء المعلّق من دالة المنشئ ثم العودة إليه. عند تمرير قيمة إلى طريقة next() في أداة إنشاء كوسيطة، يتم استبدال القيمة المرتبطة بالتعبير yield السابق المعلّق:

function* myGeneratorFunction() {
    const firstYield = yield;
    yield firstYield + 10;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject.next();
> Object { value: undefined, done: false }

myGeneratorObject.next( 5 );
> Object { value: 15, done: false }

يُرجى العِلم أنّ هذا الإجراء يستبدل التعبير الكامل المرتبط بالسمة yield السابقة، ولا يؤدي فقط إلى إعادة تحديد قيمة السمة yield السابقة إلى القيمة المحدّدة في السمة next():

function* myGeneratorFunction() {
    const firstYield = yield;
    const secondYield = yield firstYield + 100;
    yield secondYield + 10;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject.next();
> Object { value: undefined, done: false }

myGeneratorObject.next( 10 ); // Can be thought of as changing the value of the `firstYield` variable to `10
> Object { value: 110, done: false }

myGeneratorObject.next( 20 ); // Can be thought of as changing the value of the `secondYield` variable to `20`, _not_ `20 + 100;`
> Object { value: 30, done: false }

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

function* myGeneratorFunction( startingValue ) {
    let newValue = yield startingValue + 1;
    newValue = yield newValue + 10;
    yield startingValue + 20;
};
const myGeneratorObject = myGeneratorFunction( 2 );

myGeneratorObject.next( 1 );
> Object { value: 3, done: false }

myGeneratorObject.next( 5 );
> Object { value: 15, done: false }

myGeneratorObject.next( 10 );
Object { value: 22, done: false }

يتم استخدام عامل التشغيل yield* (لاحظ علامة النجمة) مع دالة قابلة للتكرار، مثل دالة إنشاء أخرى، للتكرار والحصول على كل قيمة من قيم المعامل الخاصة بها:

function* mySecondaryGenerator() {
  yield 2;
  yield 3;
}

function* myGenerator() {
  yield 1;
  yield* mySecondaryGenerator();
  yield 4;
  return 5;
}

const myIterator = myGenerator();

myIterator.next();
> Object { value: 1, done: false }

myIterator.next();
> Object { value: 2, done: false }

myIterator.next();
> Object { value: 3, done: false }

myIterator.next();
> Object { value: 4, done: false }

myIterator.next();
> Object { value: 5, done: true }

JavaScript غير المتزامن

على الرغم من أنّ لغة JavaScript متزامنة بشكل أساسي، هناك آليات تتيح للمطوّرين الاستفادة من حلقة الأحداث لتنفيذ مهام غير متزامنة.

وعود

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

يمكنك إنشاء مثيل Promise باستخدام عامل التشغيل new مع دالة الدالة الإنشائية Promise المدمجة. تقبل الدالة الإنشائية هذه دالة تُسمى executor كوسيطة. تُستخدم هذه الوظيفة التنفيذية عادةً لتنفيذ إجراء غير متزامن واحد أو أكثر، ثم يتم إملاء البنود التي ينبغي من خلالها اعتبار الوعد أو رفضه بنجاح. يتم تعريف الوعد على أنه في انتظار المراجعة أثناء تشغيل الوظيفة التنفيذية. بعد انتهاء مُنفِّذ التنفيذ، يُعتبر الوعد مُنفذًا (أو تم حله، في بعض مصادر الوثائق) إذا تم بنجاح إنجاز المهمة المنفّذة والإجراء غير المتزامن الذي ينفّذه، ويتم رفضه إذا واجهت الدالة التنفيذية خطأً، أو فشل الإجراء غير المتزامن الذي يتم تنفيذه. بعد تحقيق الوعد أو رفضه، يتم اعتباره تم تسويته.

const myPromise = new Promise( () => { });

تستدعي الدالة الإنشائية الدالة التنفيذية مع وسيطتين. هذه الوسيطات هي دوال تتيح لك الوفاء بالوعد أو رفضه يدويًا:

const  myPromise = new Promise( ( fulfill, reject ) => { });

يتم استدعاء الدوال المستخدمة لتنفيذ الوعد أو رفضه بالقيمة الناتجة للوعد كوسيطة (عادةً ما يكون خطأ للرفض):

const myPromise = new Promise( ( fulfill, reject ) => {
  const myResult = true;
  setTimeout(() => {
    if( myResult === true ) {
        fulfill( "This Promise was successful." );    
    } else {
        reject( new Error( "This Promise has been rejected." ) );
    }
  }, 10000);
});

myPromise;
> Promise { <state>: "pending" }

myPromise;
> Promise { <state>: "fulfilled", <value>: "This Promise was successful." }

سلاسل التعهد

يمكن التعامل مع كائن Promise الناتج باستخدام الطرق then() وcatch() وfinally() المكتسَبة من الدالة الإنشائية Promise. تعرض كل طريقة من هذه الطرق الوعد، والذي يمكن تنفيذه على الفور مع then() أو catch() أو finally() مرة أخرى، ما يتيح لك سلسلة الوعود الناتجة.

توفر الدالة then() دالتين لرد الاتصال كوسيطات. استخدم الأولى للوفاء بالوعد الناتج، والثانية لرفضه. تقبل كلتا الطريقتين وسيطة واحدة تمنح الوعد الناتج قيمته.

const myPromise = new Promise( ( fulfill, reject ) => {
  const myResult = true;
  setTimeout(() => {
    if( myResult === true ) {
        fulfill( "This Promise was fulfilled." );    
    } else {
        reject( new Error( "This Promise has been rejected." ) );
    }
  }, 100);
});

myPromise.then( successfulResult => console.log( successfulResult ), failedResult => console.error( failedResult ) );
> "This Promise was successful."

يمكنك أيضًا استخدام then() للتعامل مع الحالة التي تم توصيلها فقط، وcatch للتعامل مع حالة الرفض. يمكنك استدعاء catch باستخدام وسيطة واحدة تحتوي على القيمة المقدمة في طريقة رفض Promise:

const myPromise = new Promise( ( fulfill, reject ) => {
  const myResult = false;
  setTimeout(() => {
    if( myResult === true ) {
        fulfill( "This Promise was fulfilled." );    
    } else {
        reject( new Error( "This Promise has been rejected." ) );
    }
  }, 100);
});

myPromise
  .then( fulfilledResult => console.log(fulfilledResult ) )
  .catch( rejectedResult => console.log( rejectedResult ) )
  .finally( () => console.log( "The Promise has settled." ) );
> "Error: This Promise has been rejected."
> "The Promise has settled."

على عكس then وcatch، اللذان يسمحان بتشغيل دالة المعالِج عند تنفيذ وعد أو رفضه، يتم استدعاء الدالة التي يتم تمريرها كوسيطة إلى الطريقة finally بغض النظر عما إذا كان تم استيفاء الوعد أو رفضه. يتم استدعاء دالة المعالج بدون وسيطات، لأنها ليست مخصّصة للعمل على القيم التي تم تمريرها من الوعد، فقط لتنفيذ التعليمات البرمجية بعد اكتمال الوعد.

التزامن

توفر دالة إنشاء Promise أربع طرق للعمل مع العديد من التعهدات ذات الصلة، وذلك باستخدام العنصر iterable الذي يحتوي على عناصر Promise. تعرض كل طريقة من هذه الطرق الوعد الذي يتم الوفاء به أو رفضه بناءً على حالة الوعود التي تم تمريرها إليه. على سبيل المثال، تنشئ Promise.all() وعدًا لا يتم الوفاء به إلا إذا تم تحقيق كل وعد تم تمريره إلى هذه الطريقة:

const firstPromise  = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const secondPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const thirdPromise  = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const failedPromise = new Promise( ( fulfill, reject ) => reject( "Failed.") );
const successfulPromises = [ firstPromise, secondPromise, thirdPromise ];
const oneFailedPromise = [ failedPromise, ...successfulPromises ];

Promise.all( successfulPromises )
  .then( ( allValues ) => {
    console.log( allValues );
  })
  .catch( ( failValue ) => {
    console.error( failValue );
  });
> Array(3) [ "Successful. ", "Successful. ", "Successful. " ]

Promise.all( oneFailedPromise  )
    .then( ( allValues ) => {
      console.log( allValues );
    })
    .catch( ( failValue ) => {
     console.error( failValue );
    });
> "Failed."

طرق المزامنة المضمونة هي كما يلي:

Promise.all()
يتم استيفاء جميع الوعود المقدّمة فقط.
Promise.any()
يتم استيفاء أي من الوعود المقدّمة، ويتم رفضها فقط في حال رفض جميع الوعود.
Promise.allSettled()
يتم تحقيق الوعود عند استقرارها، بغض النظر عن النتيجة.
Promise.race()
تم الرفض أو التنفيذ بناءً على نتيجة الوعد الأول بالتسوية، مع تجاهُل جميع الوعود التي تم الاتفاق عليها لاحقًا.

async/await

عند استخدام الكلمة الرئيسية async قبل بيان الدالة أو تعبير الدالة، يتم عرض أي قيمة تعرضها الدالة كوعد يتم تنفيذه ويحتوي على تلك القيمة. يتيح لك هذا تشغيل العمليات غير المتزامنة وإدارتها باستخدام نفس سيرات العمل مثل التطوير المتزامن.

async function myFunction() {
  return "This is my returned value.";
}

myFunction().then( myReturnedValue => console.log( myReturnedValue ) );
> "This is my returned value."

يوقف التعبير await تنفيذ دالة غير متزامنة مؤقتًا أثناء استقرار الوعد المرتبط. بعد تسوية الوعد، تكون قيمة تعبير await هي القيمة التي تم الوفاء بها أو رفضها.

async function myFunction() {
  const myPromise  = new Promise( ( fulfill, reject ) => { setTimeout( () => fulfill( "Successful. "), 5000 ); });
  const myPromisedResult = await myPromise;
  return myPromisedResult;
}

myFunction()
  .then( myResult => console.log( myResult ) )
  .catch( myFailedResult => console.error( myFailedResult ) );
> "Successful."

يتم عرض أي قيمة بخلاف الوعد يتم تضمينها في تعبير await على أنها وعد تم تنفيذه:

async function myFunction() {
  const myPromisedResult = await "String value.";
  return myPromisedResult;
}

myFunction()
  .then( myResult => console.log( myResult ) )
  .catch( myFailedResult => console.error( myFailedResult ) );
> "String value."

التحقق من فهمك

ما نوع التكرار الحلقي الذي تستخدمه للتكرار على كمية معروفة؟

for
while
do...while