أولًا: ما هو الـ 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");
}
Dartnumber
هو وسيط (parameter) وله نطاق داخل الدالة فقط.- لا يمكن استخدامه خارج
printSquare
.
🧩 3. Nested Function Scope (الدوال المتداخلة والـ Closures)
void outerFunction() {
int counter = 0;
void innerFunction() {
counter++;
print("Counter: $counter");
}
innerFunction(); // 1
innerFunction(); // 2
}
DartinnerFunction
يمكنها الوصول إلىcounter
لأنّه في النطاق الأعلى (enclosing scope).- هذا ما يُسمى Closure في Dart.
🧩 4. Global Scope (نطاق عام)
int globalVar = 100;
void showGlobal() {
print("Global: $globalVar");
}
DartglobalVar
متاح في أي مكان في الملف.- ⚠️ ولكن يُفضل تجنب الاستخدام المفرط للمتغيرات العامة للحفاظ على تنظيم الكود.
🧠 توضيح الفرق بين النطاقات بمثال واحد شامل:
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
}
Dartfinal
يعني لا يمكن إعادة تعيين القيمة بعد تعريفها، ولكنه لا يؤثر على النطاق نفسه.
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 لاحقًا.