(3)Scope in Function Dart

أولًا: ما هو الـ Scope؟

الـ Scope هو نطاق الرؤية أو الوصول للمتغيرات. بمعنى: من أين يمكننا الوصول إلى المتغير؟

🧠 أنواع الـ Scope داخل الدوال في Dart:
النوعالوصف
1. Local Scopeمتغيرات مُعرّفة داخل الدالة فقط.
2. Parameter Scopeالمتغيرات التي تُمرر كوسائط للدالة.
3. Nested Function Scopeدوال بداخل دوال (Closure).
4. Global Scopeالمتغيرات المعرفة خارج أي دالة (نادرًا ما تُستخدم في التطبيقات الكبيرة).
🧩 1. Local Scope (نطاق محلي)
void main() {
  int a = 5; // local to main

  void sayHello() {
    String name = "Ali"; // local to sayHello
    print("Hello $name");
  }

  sayHello();

  // print(name); // ❌ خطأ: name غير معروف هنا
}
Dart
  • name يمكن الوصول له فقط داخل sayHello.
  • ❌ لا يمكن الوصول له خارج sayHello.
🧩 2. Parameter Scope (نطاق المعاملات)
void printSquare(int number) {
  int square = number * number;
  print("Square is $square");
}
Dart
  • number هو وسيط (parameter) وله نطاق داخل الدالة فقط.
  • لا يمكن استخدامه خارج printSquare.
🧩 3. Nested Function Scope (الدوال المتداخلة والـ Closures)
void outerFunction() {
  int counter = 0;

  void innerFunction() {
    counter++;
    print("Counter: $counter");
  }

  innerFunction(); // 1
  innerFunction(); // 2
}
Dart
  • innerFunction يمكنها الوصول إلى counter لأنّه في النطاق الأعلى (enclosing scope).
  • هذا ما يُسمى Closure في Dart.
🧩 4. Global Scope (نطاق عام)
int globalVar = 100;

void showGlobal() {
  print("Global: $globalVar");
}
Dart
  • globalVar متاح في أي مكان في الملف.
  • ⚠️ ولكن يُفضل تجنب الاستخدام المفرط للمتغيرات العامة للحفاظ على تنظيم الكود.
🧠 توضيح الفرق بين النطاقات بمثال واحد شامل:
int global = 10;

void main() {
  int a = 5; // local to main

  void inner() {
    int b = 7; // local to inner
    print(global); // ✅ يمكن الوصول إلى global
    print(a); // ✅ يمكن الوصول إلى a
    print(b); // ✅ داخل inner فقط
  }

  inner();
  // print(b); // ❌ لا يمكن الوصول
}
Dart
📌 ماذا عن المتغيرات داخل if/for/while؟

هذه المتغيرات تُعتبر block scoped أيضًا.

void main() {
  if (true) {
    int x = 50;
    print(x); // ✅
  }

  // print(x); // ❌ x خارج النطاق
}
Dart
📝 ملاحظات مهمة:
الملاحظةالتوضيح
كل {} تخلق Scope جديدسواء داخل if, for, أو function.
يمكن للدالة الداخلية الوصول لمتغيرات الدالة الأموهو ما يُسمى Closure.
المتغير المحلي له الأولويةإذا كان هناك متغير بنفس الاسم في Scope أعلى.
🎯 هل يمكن تغيير متغير من نطاق أعلى؟

نعم باستخدام Closure:

Function counterMaker() {
  int count = 0;

  return () {
    count++;
    print("Count: $count");
  };
}

void main() {
  var counter = counterMaker();
  counter(); // 1
  counter(); // 2
}
Dart
✅ خلاصة:
النوعالوصول
Local Scopeداخل الدالة فقط
Parameter Scopeداخل الدالة
Nested/Closureداخل الدالة الداخلية ويمكن الوصول لمتغيرات الدالة الأم
Global Scopeفي جميع الدوال في الملف


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

⚠️ نقاط إضافية مهمة عن Scope في Dart:
1. لا يوجد Block Scope للمتحولات باستخدام var في بعض الحالات القديمة

في Dart الحديثة (Dart 2 فما فوق)، كل {} يُنشئ Scope. ولكن سابقًا أو في بعض اللغات الأخرى، هذا غير مؤكد.

void main() {
  if (true) {
    var x = 5;
  }

  // print(x); ❌ خطأ: x خارج النطاق
}
Dart

✅ Dart تدعم block-level scope باستخدام {}.

2. نطاق المتغيرات داخل for, while, و switch
void main() {
  for (int i = 0; i < 3; i++) {
    print(i);
  }

  // print(i); ❌ i غير معرف خارج الـ for
}
Dart
  • كل متغير int i = 0 داخل for له scope خاص.
3. لا يمكن تعريف متغيرين بنفس الاسم في نفس الـ Scope
void main() {
  var name = "Ali";
  // var name = "Sara"; // ❌ Error: Duplicate variable
}
Dart
  • حتى لو كانت القيم مختلفة، لا يُسمح بتكرار الاسم داخل نفس النطاق.
4. متغيرات الدوال لا تتصادم مع المتغيرات الخارجية (Shadowing)
int x = 100;

void show() {
  int x = 5; // ✅ لا مشكلة: هذا "shadows" المتغير الخارجي
  print(x);  // 5
}
Dart
  • المتغير الداخلي يغطّي على المتغير الخارجي داخل النطاق فقط.
5. المتغيرات النهائية داخل النطاقات (final)
void main() {
  final a = 10;
  // a = 20; ❌ لا يمكن تغييره لأنه final
}
Dart
  • final يعني لا يمكن إعادة تعيين القيمة بعد تعريفها، ولكنه لا يؤثر على النطاق نفسه.
6. لا يمكن الوصول إلى متغير معرف بعد استخدامه
void main() {
  // print(x); ❌ خطأ: x لم يتم تعريفها بعد
  int x = 10;
}
Dart
  • Dart تقرأ الكود من الأعلى إلى الأسفل، لا يوجد “hoisting” كما في JavaScript.
7. يمكن استخدام الكلمة المفتاحية late لتأجيل التهيئة ولكن داخل النطاق الصحيح
late String name;

void main() {
  name = "Ali";
  print(name); // ✅
}
Dart

late تسمح بتعريف متغير بدون تهيئة فورية، ولكن يجب الانتباه للنطاق.

8. داخل الكلاسات، Scope يختلف قليلًا

داخل الكلاسات هناك نطاق العضو (member scope)، وسنتحدث عنه عندما نتطرق إلى OOP، لكن باختصار:

class User {
  String name = "Ali";

  void showName() {
    print(name); // ✅ متاح في كامل الكلاس
  }
}
Dart
✅ هل يجب أن تحفظ كل هذه القواعد؟

لا، لكن فهمها يسهل عليك:

  • تصحيح الأخطاء.
  • كتابة كود منظم وآمن.
  • التعامل مع الـ closures، الكلاسات، والـ state management لاحقًا.