الكائنات: الانشاء والوصول الى الخصائص
ما هي الكائنات في JavaScript؟
الكائنات هي احد انواع البيانات الاساسية في JavaScript. بينما تخزن المصفوفات مجموعات مرتبة من القيم يتم الوصول اليها بالفهرس تخزن الكائنات ازواج المفتاح والقيمة حيث ترتبط كل قيمة بمفتاح مسمى (يسمى ايضا خاصية). الكائنات هي لبنات البناء لكل تطبيق JavaScript تقريبا -- فهي تمثل المستخدمين والمنتجات والاعدادات واستجابات واجهات برمجة التطبيقات وعناصر DOM واي كيان تحتاج لنمذجته في كودك. فهم كيفية انشاء الكائنات والوصول اليها وتعديلها والتعامل معها امر ضروري لكل مطور JavaScript.
في JavaScript كل شيء تقريبا هو كائن او يتصرف مثل واحد. المصفوفات كائنات والدوال كائنات والتواريخ كائنات وحتى القيم البدائية مثل النصوص والارقام لها اغلفة كائنات تمنحها الوصول الى الدوال. عندما يقول الناس ان JavaScript لغة "موجهة للكائنات" فهذا الاستخدام الواسع للكائنات هو ما يقصدونه. في هذا الدرس سنركز على الكائنات العادية (تسمى ايضا كائنات حرفية) -- النوع الاكثر شيوعا وتنوعا من الكائنات الذي ستستخدمه في التطوير اليومي.
تختلف الكائنات عن المصفوفات بعدة طرق مهمة. المصفوفات مرتبة ويتم الوصول اليها بفهرس رقمي. الكائنات مجموعات غير مرتبة يتم الوصول اليها بمفاتيح نصية. المصفوفات مثالية لقوائم العناصر المتشابهة (قائمة منتجات او قائمة درجات). الكائنات مثالية لتمثيل كيان واحد بخصائص متعددة مسماة (مستخدم واحد باسم وبريد الكتروني وعمر). الاختيار بين المصفوفة والكائن يعتمد على ما تمثله بياناتك وكيف تحتاج للوصول اليها.
انشاء الكائنات بالصيغة الحرفية
ابسط واشيع طريقة لانشاء كائن في JavaScript هي باستخدام الصيغة الحرفية للكائن -- زوج من الاقواس المعقوفة يحتوي على صفر او اكثر من ازواج المفتاح والقيمة مفصولة بفواصل. يتبع كل مفتاح نقطتان ثم القيمة المقابلة. المفاتيح عادة نصوص (على الرغم من انها يمكن ان تكون رموزا) والقيم يمكن ان تكون اي نوع بيانات صالح في JavaScript بما في ذلك النصوص والارقام والقيم المنطقية والمصفوفات والدوال وحتى كائنات اخرى.
مثال: انشاء كائنات بالصيغة الحرفية
// كائن فارغ
const emptyObj = {};
// كائن بعدة خصائص
const person = {
firstName: 'احمد',
lastName: 'حسن',
age: 28,
isStudent: false,
hobbies: ['القراءة', 'البرمجة', 'المشي'],
address: {
city: 'عمان',
country: 'الاردن'
}
};
// كائن بانواع قيم مختلفة
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retryCount: 3,
debug: true,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
};
قواعد مفاتيح الخصائص
تتبع مفاتيح الخصائص في كائنات JavaScript قواعد محددة. اذا كان المفتاح معرفا صالحا (يبدا بحرف او شرطة سفلية او علامة دولار ويحتوي فقط على احرف وارقام وشرطات سفلية او علامات دولار) يمكنك كتابته بدون علامات اقتباس. اذا كان المفتاح يحتوي على مسافات او شرطات او يبدا برقم او كان كلمة محجوزة يجب وضعه بين علامات اقتباس.
مثال: مفاتيح خصائص صالحة ومقتبسة
const product = {
name: 'لابتوب', // معرف صالح -- لا حاجة لعلامات اقتباس
price: 999, // معرف صالح
_id: 'abc123', // صالح (يبدا بشرطة سفلية)
$currency: 'USD', // صالح (يبدا بعلامة دولار)
'in-stock': true, // الشرطة تتطلب علامات اقتباس
'item count': 42, // المسافة تتطلب علامات اقتباس
'2ndChoice': 'شاشة', // يبدا برقم يتطلب علامات اقتباس
'class': 'مميز' // كلمة محجوزة -- يوصى بعلامات اقتباس
};
name هو نفسه 'name'. المفاتيح الرقمية ايضا تحول الى نصوص -- { 1: 'واحد' } تخزن المفتاح كالنص '1'.الوصول للخصائص: النقطة مقابل الاقواس
هناك طريقتان للوصول الى خصائص الكائن: صيغة النقطة وصيغة الاقواس. فهم متى تستخدم كل واحدة امر حاسم للعمل بفعالية مع الكائنات.
صيغة النقطة
صيغة النقطة هي الطريقة الاكثر شيوعا وقراءة للوصول الى خصائص الكائن. تكتب اسم الكائن متبوعا بنقطة ثم اسم الخاصية. تعمل صيغة النقطة فقط عندما يكون اسم الخاصية معرف JavaScript صالحا.
مثال: صيغة النقطة
const user = {
name: 'سارة علي',
email: 'sara@example.com',
age: 25,
isActive: true
};
// قراءة الخصائص
console.log(user.name); // 'سارة علي'
console.log(user.email); // 'sara@example.com'
console.log(user.age); // 25
console.log(user.isActive); // true
// الوصول الى كائنات متداخلة
const company = {
name: 'تيك كورب',
address: {
street: '123 الشارع الرئيسي',
city: 'دبي',
zip: '12345'
}
};
console.log(company.address.city); // 'دبي'
console.log(company.address.street); // '123 الشارع الرئيسي'
صيغة الاقواس
تستخدم صيغة الاقواس اقواسا مربعة مع نص (او تعبير يقيم الى نص) للوصول الى الخصائص. صيغة الاقواس اكثر مرونة من صيغة النقطة لانها تعمل مع اي مفتاح نصي بما في ذلك تلك التي تحتوي على مسافات او شرطات او قيم ديناميكية مخزنة في متغيرات.
مثال: صيغة الاقواس
const user = {
name: 'عمر خان',
'favorite-color': 'ازرق',
'home address': '123 شارع النخيل',
age: 30
};
// صيغة الاقواس مع مفاتيح نصية
console.log(user['name']); // 'عمر خان'
console.log(user['favorite-color']); // 'ازرق' -- لا يمكن استخدام صيغة النقطة هنا
console.log(user['home address']); // '123 شارع النخيل' -- لا يمكن استخدام صيغة النقطة
// الوصول الديناميكي للخصائص بالمتغيرات
const propertyName = 'age';
console.log(user[propertyName]); // 30
// استخدام التعبيرات
const prefix = 'favorite';
console.log(user[prefix + '-color']); // 'ازرق'
// التكرار والوصول ديناميكيا
const keys = ['name', 'age'];
keys.forEach(function(key) {
console.log(key + ': ' + user[key]);
});
// المخرج: name: عمر خان, age: 30
اسماء الخصائص المحسوبة
قدم ES6 اسماء الخصائص المحسوبة التي تتيح لك استخدام تعبير داخل اقواس مربعة كمفتاح خاصية عند انشاء كائن حرفي. هذا مفيد للغاية عندما تحتاج لانشاء كائنات بمفاتيح ديناميكية -- مثلا عند بناء كائنات من ادخال المستخدم او استجابات واجهات برمجة التطبيقات او بيانات التكوين.
مثال: اسماء الخصائص المحسوبة
// خاصية محسوبة اساسية
const key = 'color';
const value = 'red';
const obj = {
[key]: value
};
console.log(obj); // { color: 'red' }
// استخدام التعبيرات في الخصائص المحسوبة
const prefix = 'user';
const userSettings = {
[prefix + 'Name']: 'احمد',
[prefix + 'Email']: 'ahmad@example.com',
[prefix + 'Role']: 'admin'
};
console.log(userSettings);
// { userName: 'احمد', userEmail: 'ahmad@example.com', userRole: 'admin' }
// معالجة بيانات النموذج ديناميكيا
function createFormData(fieldName, fieldValue) {
return {
[fieldName]: fieldValue,
[fieldName + '_updated']: new Date().toISOString()
};
}
const data = createFormData('email', 'test@example.com');
console.log(data);
// بناء كائنات بحث ديناميكيا
const categories = ['الكترونيات', 'ملابس', 'كتب'];
const categoryFlags = categories.reduce(function(flags, cat) {
return { ...flags, ['show_' + cat]: true };
}, {});
console.log(categoryFlags);
الصيغة المختصرة للخصائص والدوال
قدم ES6 صيغتين مختصرتين تجعلان انشاء الكائنات اكثر ايجازا. عندما تاتي قيمة خاصية من متغير بنفس اسم المفتاح يمكنك استخدام اختصار الخاصية. عند تعريف الدوال (دوال كقيم خصائص) يمكنك استخدام اختصار الدالة الذي يحذف كلمة function والنقطتين.
مثال: اختصار الخصائص والدوال
// اختصار الخاصية
const name = 'ليلى';
const age = 27;
const city = 'الرياض';
// بدون اختصار (الطريقة القديمة)
const personOld = {
name: name,
age: age,
city: city
};
// مع الاختصار (الطريقة الحديثة)
const personNew = { name, age, city };
console.log(personNew); // { name: 'ليلى', age: 27, city: 'الرياض' }
// اختصار الدالة
// بدون اختصار
const calculatorOld = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
}
};
// مع الاختصار
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};
console.log(calculator.add(10, 5)); // 15
console.log(calculator.subtract(10, 5)); // 5
// الجمع بين الاختصارين
function createUser(name, email, role) {
return {
name,
email,
role,
createdAt: new Date().toISOString(),
greet() {
return 'مرحبا، انا ' + this.name;
}
};
}
const user = createUser('احمد', 'ahmad@example.com', 'admin');
console.log(user.greet()); // 'مرحبا، انا احمد'
اضافة وتعديل وحذف الخصائص
كائنات JavaScript قابلة للتغيير -- يمكنك اضافة خصائص جديدة وتغيير الموجودة وحذف خصائص في اي وقت بعد انشاء الكائن. هذا صحيح حتى للكائنات المعرفة بـ const لان const تمنع اعادة تعيين المتغير وليس تعديل الكائن الذي يشير اليه.
مثال: اضافة وتعديل الخصائص
const car = {
make: 'تويوتا',
model: 'كامري',
year: 2023
};
// اضافة خصائص جديدة
car.color = 'فضي';
car['mileage'] = 15000;
car.features = ['بلوتوث', 'كاميرا خلفية'];
console.log(car);
// تعديل الخصائص الموجودة
car.year = 2024;
car.mileage = 16500;
console.log(car.year); // 2024
console.log(car.mileage); // 16500
// حذف الخصائص
delete car.mileage;
console.log(car.mileage); // undefined
// const تمنع اعادة التعيين وليس التعديل
const settings = { theme: 'dark' };
settings.theme = 'light'; // هذا يعمل -- تعديل الكائن
settings.fontSize = 16; // هذا يعمل -- اضافة خاصية
// settings = { theme: 'new' }; // TypeError: تعيين لمتغير ثابت
const تجعل الكائن غير قابل للتغيير. كلمة const تمنع فقط اعادة تعيين المتغير لقيمة مختلفة. الكائن نفسه لا يزال قابلا للتعديل. اذا كنت تحتاج كائنا غير قابل للتغيير فعلا استخدم Object.freeze() لكن كن على علم انها تنفذ تجميدا سطحيا فقط -- الكائنات المتداخلة لا تزال قابلة للتعديل.التحقق من وجود الخصائص
قبل الوصول الى خاصية غالبا ما تحتاج للتحقق مما اذا كانت موجودة في الكائن. يوفر JavaScript عدة طرق للقيام بذلك كل منها بسلوك مختلف قليلا.
عامل in
يتحقق عامل in مما اذا كانت خاصية موجودة في كائن بما في ذلك الخصائص الموروثة من سلسلة النموذج الاولي. يعيد true او false.
دالة hasOwnProperty
تتحقق دالة hasOwnProperty مما اذا كان الكائن يملك الخاصية المحددة كخاصية خاصة به (غير موروثة من سلسلة النموذج الاولي). هذا هو الخيار الاكثر امانا عندما تريد التحقق فقط من الخصائص التي تنتمي مباشرة الى الكائن.
مثال: التحقق من وجود الخصائص
const user = {
name: 'سارة',
email: 'sara@example.com',
age: 25,
address: undefined
};
// استخدام عامل in
console.log('name' in user); // true
console.log('email' in user); // true
console.log('phone' in user); // false
console.log('address' in user); // true (موجودة حتى لو القيمة undefined)
console.log('toString' in user); // true (موروثة من نموذج Object الاولي)
// استخدام hasOwnProperty
console.log(user.hasOwnProperty('name')); // true
console.log(user.hasOwnProperty('phone')); // false
console.log(user.hasOwnProperty('address')); // true
console.log(user.hasOwnProperty('toString')); // false (موروثة وليست خاصة)
// التحقق قبل الوصول -- تجنب الاخطاء
function getUserCity(user) {
if (user.address && user.address.city) {
return user.address.city;
}
return 'المدينة غير متاحة';
}
// النهج الحديث: التسلسل الاختياري (ES2020)
function getUserCityModern(user) {
return user?.address?.city ?? 'المدينة غير متاحة';
}
// نمط عملي: الوصول الامن للخصائص
const config = { database: { host: 'localhost', port: 3306 } };
const dbHost = ('database' in config) ? config.database.host : 'default-host';
console.log(dbHost); // 'localhost'
undefined. عامل in و hasOwnProperty كلاهما يعيدان true للخصائص المعينة الى undefined بينما التحقق البسيط من القيمة (if (obj.prop)) سيعيد false. استخدم الفحص المناسب لحالتك المحددة.Object.keys و Object.values و Object.entries
يوفر JavaScript ثلاث دوال ثابتة على مُنشئ Object تتيح لك استخراج المفاتيح او القيم او ازواج المفتاح والقيمة من كائن كمصفوفات. هذه الدوال مفيدة بشكل لا يصدق للتكرار على الكائنات وتحويل بياناتها والتحويل بين الكائنات والمصفوفات.
مثال: Object.keys و Object.values و Object.entries
const product = {
name: 'لوحة مفاتيح ميكانيكية',
price: 89.99,
brand: 'كي ماستر',
inStock: true,
rating: 4.7
};
// Object.keys -- تعيد مصفوفة اسماء الخصائص
const keys = Object.keys(product);
console.log(keys);
// ['name', 'price', 'brand', 'inStock', 'rating']
// Object.values -- تعيد مصفوفة قيم الخصائص
const values = Object.values(product);
console.log(values);
// ['لوحة مفاتيح ميكانيكية', 89.99, 'كي ماستر', true, 4.7]
// Object.entries -- تعيد مصفوفة ازواج [مفتاح، قيمة]
const entries = Object.entries(product);
console.log(entries);
// [
// ['name', 'لوحة مفاتيح ميكانيكية'],
// ['price', 89.99],
// ['brand', 'كي ماستر'],
// ['inStock', true],
// ['rating', 4.7]
// ]
// التكرار على كائن باستخدام forEach
Object.keys(product).forEach(function(key) {
console.log(key + ': ' + product[key]);
});
// استخدام entries مع التفكيك
Object.entries(product).forEach(function([key, value]) {
console.log(key + ' = ' + value);
});
// تحويل كائن الى مصفوفة نصوص منسقة
const summary = Object.entries(product)
.map(function([key, value]) {
return key + ': ' + value;
})
.join(', ');
console.log(summary);
// عد الخصائص
console.log('عدد الخصائص: ' + Object.keys(product).length); // 5
Object.assign ودمج الكائنات
تنسخ دالة Object.assign جميع الخصائص الخاصة القابلة للعد من كائن مصدر واحد او اكثر الى كائن هدف. تعيد الكائن الهدف المعدل. تستخدم هذه الدالة بشكل شائع لدمج الكائنات وانشاء نسخ وتطبيق الاعدادات الافتراضية.
مثال: Object.assign
// دمج الكائنات
const defaults = {
theme: 'light',
fontSize: 14,
language: 'en',
notifications: true
};
const userPrefs = {
theme: 'dark',
fontSize: 18
};
// دمج تفضيلات المستخدم مع الافتراضيات
const settings = Object.assign({}, defaults, userPrefs);
console.log(settings);
// { theme: 'dark', fontSize: 18, language: 'en', notifications: true }
// تفضيلات المستخدم تتجاوز الافتراضيات
// انشاء نسخة سطحية
const original = { a: 1, b: 2, c: { nested: true } };
const copy = Object.assign({}, original);
copy.a = 99;
console.log(original.a); // 1 -- القيم البدائية مستقلة
copy.c.nested = false;
console.log(original.c.nested); // false -- الكائنات المتداخلة تشارك نفس المرجع!
// كائنات مصدر متعددة (المصادر اللاحقة تتجاوز السابقة)
const base = { a: 1, b: 2 };
const override1 = { b: 3, c: 4 };
const override2 = { c: 5, d: 6 };
const merged = Object.assign({}, base, override1, override2);
console.log(merged); // { a: 1, b: 3, c: 5, d: 6 }
صيغة نشر الكائنات
يوفر عامل النشر (...) بديلا اكثر حداثة وقراءة لـ Object.assign لنسخ ودمج الكائنات. تم تقديمه في ES2018 وينشئ نشر الكائنات كائنا جديدا عن طريق نشر خصائص الكائنات الموجودة فيه. مثل Object.assign ينفذ نسخا سطحيا.
مثال: نشر الكائنات
// نسخ كائن
const original = { name: 'سارة', age: 25 };
const copy = { ...original };
console.log(copy); // { name: 'سارة', age: 25 }
// دمج الكائنات
const defaults = { theme: 'light', lang: 'en', debug: false };
const userPrefs = { theme: 'dark', debug: true };
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: 'dark', lang: 'en', debug: true }
// اضافة خصائص جديدة اثناء النسخ
const baseProduct = { name: 'اداة', price: 10 };
const featuredProduct = {
...baseProduct,
featured: true,
discount: 0.1
};
console.log(featuredProduct);
// { name: 'اداة', price: 10, featured: true, discount: 0.1 }
// تجاوز خصائص محددة
const user = { name: 'احمد', email: 'ahmad@old.com', role: 'user' };
const updatedUser = { ...user, email: 'ahmad@new.com', role: 'admin' };
console.log(updatedUser);
// { name: 'احمد', email: 'ahmad@new.com', role: 'admin' }
// خصائص مشروطة باستخدام النشر
const isAdmin = true;
const userObj = {
name: 'عمر',
email: 'omar@example.com',
...(isAdmin ? { role: 'admin', permissions: ['read', 'write', 'delete'] } : { role: 'user' })
};
console.log(userObj);
Object.assign في JavaScript الحديث. فهي اكثر قراءة وتنشئ كائنا جديدا بشكل افتراضي (لا حاجة للكائن الفارغ {} كمعامل اول) وتتكامل بسلاسة مع عمليات النشر الاخرى على المصفوفات. كلاهما ينفذ نسخا سطحيا لذلك الكائنات المتداخلة لا تزال تشارك المراجع.العمل مع الكائنات المتداخلة
البيانات الحقيقية نادرا ما تكون مسطحة. تحتوي الكائنات بشكل متكرر على كائنات اخرى ومصفوفات كائنات وهياكل متداخلة بعمق. العمل مع الكائنات المتداخلة يتطلب فهم كيفية الوصول الامن للخصائص العميقة وكيفية تعديل القيم المتداخلة دون تغيير الاصل وكيفية التعامل مع الخصائص الوسيطة المفقودة او غير المعرفة.
مثال: الكائنات المتداخلة
const company = {
name: 'تيك ستارت اب',
founded: 2020,
headquarters: {
address: {
street: '456 شارع الابتكار',
city: 'دبي',
country: 'الامارات'
},
contact: {
phone: '+971-4-123-4567',
email: 'info@techstartup.com'
}
},
departments: {
engineering: {
head: 'احمد',
teamSize: 15,
projects: ['الموقع', 'التطبيق', 'واجهة البرمجة']
},
marketing: {
head: 'سارة',
teamSize: 8,
projects: ['اعادة العلامة', 'التواصل الاجتماعي']
}
}
};
// الوصول الى قيم متداخلة بعمق
console.log(company.headquarters.address.city); // 'دبي'
console.log(company.departments.engineering.head); // 'احمد'
console.log(company.departments.engineering.projects[0]); // 'الموقع'
// الوصول الامن بالتسلسل الاختياري
const salesHead = company.departments?.sales?.head ?? 'غير معين';
console.log(salesHead); // 'غير معين'
// تعديل القيم المتداخلة (يغير الاصل)
company.departments.engineering.teamSize = 18;
company.departments.engineering.projects.push('لوحة التحكم');
// انشاء نسخة معدلة دون تغيير الاصل
const updatedCompany = {
...company,
headquarters: {
...company.headquarters,
contact: {
...company.headquarters.contact,
phone: '+971-4-999-8888'
}
}
};
console.log(company.headquarters.contact.phone); // '+971-4-123-4567' (لم يتغير)
console.log(updatedCompany.headquarters.contact.phone); // '+971-4-999-8888' (نسخة محدثة)
undefined او null في سلسلة متداخلة ستطرح TypeError. مثلا company.departments.sales.head سيتعطل اذا لم يكن sales موجودا. استخدم دائما التسلسل الاختياري (?.) او تحقق من كل مستوى قبل الوصول الى التالي عند العمل مع بيانات متداخلة قد تحتوي على خصائص مفقودة.مقارنة الكائنات: المرجع مقابل القيمة
احد اهم المفاهيم واكثرها سوء فهم في JavaScript هو كيفية مقارنة الكائنات. على عكس القيم البدائية (الارقام والنصوص والقيم المنطقية) التي تقارن بـالقيمة تقارن الكائنات بـالمرجع. كائنان بخصائص وقيم متطابقة ليسا متساويين ما لم يشيرا الى نفس الكائن بالضبط في الذاكرة.
مثال: مقارنة المرجع مقابل القيمة
// مقارنة القيم البدائية: بالقيمة
const a = 5;
const b = 5;
console.log(a === b); // true -- نفس القيمة
const str1 = 'مرحبا';
const str2 = 'مرحبا';
console.log(str1 === str2); // true -- نفس القيمة
// مقارنة الكائنات: بالمرجع
const obj1 = { name: 'احمد', age: 30 };
const obj2 = { name: 'احمد', age: 30 };
console.log(obj1 === obj2); // false! كائنات مختلفة في الذاكرة
// نفس المرجع
const obj3 = obj1;
console.log(obj1 === obj3); // true -- كلاهما يشير الى نفس الكائن
// التعديل عبر مرجع واحد يؤثر على الاخر
obj3.age = 31;
console.log(obj1.age); // 31 -- تاثر obj1 لانهما يشاركان مرجعا
// المصفوفات كائنات ايضا -- نفس سلوك المرجع
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false
// كيفية مقارنة الكائنات بالقيمة
// الطريقة 1: JSON.stringify (تعمل للكائنات البسيطة)
const user1 = { name: 'سارة', age: 25 };
const user2 = { name: 'سارة', age: 25 };
console.log(JSON.stringify(user1) === JSON.stringify(user2)); // true
// الطريقة 2: دالة مقارنة يدوية
function shallowEqual(objA, objB) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;
return keysA.every(function(key) {
return objB.hasOwnProperty(key) && objA[key] === objB[key];
});
}
console.log(shallowEqual(user1, user2)); // true
JSON.stringify لمقارنة الكائنات له قيود. لا يعمل مع قيم undefined (يتم حذفها) والدوال (يتم حذفها) وكائنات Date (تحول الى نصوص) والمراجع الدائرية (يطرح خطا). كما يتطلب ان تكون الخصائص بنفس الترتيب. للكود الانتاجي فكر في استخدام دالة مساواة عميقة من مكتبة مثل Lodash.التكرار على الكائنات
على عكس المصفوفات لا تملك الكائنات دوال تكرار مدمجة مثل forEach او map. ومع ذلك هناك عدة انماط فعالة للتكرار على خصائص الكائنات.
مثال: طرق مختلفة للتكرار على الكائنات
const scores = {
math: 92,
science: 88,
english: 95,
history: 79,
art: 97
};
// الطريقة 1: حلقة for...in
for (const subject in scores) {
if (scores.hasOwnProperty(subject)) {
console.log(subject + ': ' + scores[subject]);
}
}
// الطريقة 2: Object.keys مع forEach
Object.keys(scores).forEach(function(subject) {
console.log(subject + ': ' + scores[subject]);
});
// الطريقة 3: Object.entries مع forEach (الاكثر حداثة)
Object.entries(scores).forEach(function([subject, score]) {
console.log(subject + ': ' + score);
});
// الطريقة 4: Object.entries مع for...of
for (const [subject, score] of Object.entries(scores)) {
console.log(subject + ': ' + score);
}
// تحويل الكائنات باستخدام Object.entries و Object.fromEntries
const curved = Object.fromEntries(
Object.entries(scores).map(function([subject, score]) {
return [subject, Math.min(score + 5, 100)];
})
);
console.log(curved);
// { math: 97, science: 93, english: 100, history: 84, art: 100 }
انماط الكائنات في العالم الحقيقي
دعنا نلقي نظرة على امثلة شاملة توضح كيف تستخدم الكائنات في تطبيقات JavaScript الحقيقية من ادارة حالة التطبيق الى معالجة بيانات واجهات برمجة التطبيقات وبناء تكوينات ديناميكية.
مثال: ادارة ملف المستخدم
// انشاء ملف مستخدم مع دوال
const userProfile = {
id: 1,
firstName: 'احمد',
lastName: 'حسن',
email: 'ahmad@example.com',
preferences: {
theme: 'dark',
language: 'ar',
notifications: {
email: true,
push: false,
sms: true
}
},
getFullName() {
return this.firstName + ' ' + this.lastName;
},
updatePreference(path, value) {
const keys = path.split('.');
let current = this.preferences;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
},
toJSON() {
return {
id: this.id,
name: this.getFullName(),
email: this.email,
preferences: { ...this.preferences }
};
}
};
console.log(userProfile.getFullName()); // 'احمد حسن'
userProfile.updatePreference('notifications.push', true);
console.log(userProfile.preferences.notifications.push); // true
console.log(userProfile.toJSON());
مثال: بناء تكوين ديناميكي
// بناء كائن تكوين ديناميكيا
function createApiConfig(options) {
const defaults = {
baseUrl: 'https://api.example.com',
version: 'v1',
timeout: 5000,
retries: 3,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
// الدمج مع خيارات المستخدم
const config = {
...defaults,
...options,
headers: {
...defaults.headers,
...(options.headers || {})
}
};
// اضافة خصائص محسوبة
config.fullUrl = config.baseUrl + '/' + config.version;
// اضافة دوال
config.getEndpoint = function(path) {
return this.fullUrl + '/' + path;
};
return config;
}
const apiConfig = createApiConfig({
baseUrl: 'https://myapi.com',
headers: { 'Authorization': 'Bearer token123' }
});
console.log(apiConfig.fullUrl);
// 'https://myapi.com/v1'
console.log(apiConfig.getEndpoint('users'));
// 'https://myapi.com/v1/users'
console.log(apiConfig.headers);
مثال: معالجة واعادة هيكلة بيانات API
// استجابة API محاكاة مع بيانات متداخلة
const apiResponse = {
status: 200,
data: {
users: [
{ id: 1, name: 'احمد', department_id: 101, skills: ['js', 'python'] },
{ id: 2, name: 'سارة', department_id: 102, skills: ['design', 'css'] },
{ id: 3, name: 'عمر', department_id: 101, skills: ['js', 'node'] },
{ id: 4, name: 'ليلى', department_id: 103, skills: ['marketing'] }
],
departments: [
{ id: 101, name: 'الهندسة' },
{ id: 102, name: 'التصميم' },
{ id: 103, name: 'التسويق' }
]
}
};
// انشاء كائن بحث اقسام للوصول O(1)
const deptLookup = apiResponse.data.departments.reduce(function(lookup, dept) {
lookup[dept.id] = dept.name;
return lookup;
}, {});
console.log(deptLookup); // { 101: 'الهندسة', 102: 'التصميم', 103: 'التسويق' }
// اثراء بيانات المستخدمين باسماء الاقسام
const enrichedUsers = apiResponse.data.users.map(function(user) {
return {
...user,
departmentName: deptLookup[user.department_id] || 'غير معروف',
skillCount: user.skills.length
};
});
console.log(enrichedUsers);
// تجميع المستخدمين حسب القسم
const usersByDept = enrichedUsers.reduce(function(groups, user) {
const dept = user.departmentName;
if (!groups[dept]) {
groups[dept] = [];
}
groups[dept].push({ name: user.name, skills: user.skills });
return groups;
}, {});
console.log(usersByDept);
// جمع كل المهارات الفريدة عبر جميع المستخدمين
const allSkills = apiResponse.data.users
.reduce(function(skills, user) {
user.skills.forEach(function(skill) {
if (!skills.includes(skill)) {
skills.push(skill);
}
});
return skills;
}, []);
console.log(allSkills); // ['js', 'python', 'design', 'css', 'node', 'marketing']
تمرين عملي
ابن نظام ادارة جهات اتصال صغير باستخدام الكائنات. اكمل المهام التالية:
- انشئ كائن
contactsيخزن 5 كائنات جهات اتصال على الاقل كل منها بخصائصnameوemailوphoneوcategory(واحد من 'عائلة' او 'عمل' او 'صديق') وكائنaddressبـcityوcountry. - اكتب دالة
addContactتاخذ كائن جهات الاتصال واسما وكائن تفاصيل جهة الاتصال ثم تضيف جهة الاتصال الجديدة باستخدام اسماء الخصائص المحسوبة. - اكتب دالة
findByCityتستخدمObject.valuesوfilterلارجاع جميع جهات الاتصال في مدينة محددة. - اكتب دالة
getContactSummaryتستخدمObject.entriesوmapلارجاع مصفوفة نصوص منسقة مثل "الاسم (الفئة): البريد الالكتروني". - اكتب دالة
groupByCategoryتستخدمObject.valuesوreduceلتجميع جهات الاتصال حسب فئتها. - اكتب دالة
mergeContactsتاخذ كائني جهات اتصال وتدمجهما باستخدام عامل النشر مع تجاوز الكائن الثاني للمفاتيح المكررة. - استخدم
Object.keysلعد عدد جهات الاتصال في كل فئة. - انشئ نسخة عميقة من جهة اتصال واحدة وعدل عنوان النسخة وتحقق من ان الاصل لم يتغير.