JavaScript المتقدم (ES6+)

دوال الكائنات

13 دقيقة الدرس 29 من 40

دوال الكائنات

توفر JavaScript مجموعة غنية من الدوال المدمجة للعمل مع الكائنات. تتيح لك هذه الدوال التكرار على خصائص الكائنات، ودمج الكائنات، والتحكم في سلوك الخصائص، والمزيد. دعنا نستكشف دوال الكائنات الأكثر فائدة في JavaScript الحديثة.

Object.keys() و Object.values() و Object.entries()

توفر هذه الدوال طرقاً مختلفة للتكرار على خصائص الكائنات:

const user = { name: "John Doe", age: 30, email: "john@example.com", role: "developer" }; // Object.keys() - تُرجع مصفوفة من أسماء الخصائص const keys = Object.keys(user); console.log(keys); // ["name", "age", "email", "role"] // Object.values() - تُرجع مصفوفة من قيم الخصائص const values = Object.values(user); console.log(values); // ["John Doe", 30, "john@example.com", "developer"] // Object.entries() - تُرجع مصفوفة من أزواج [key, value] const entries = Object.entries(user); console.log(entries); // [["name", "John Doe"], ["age", 30], ["email", "john@example.com"], ["role", "developer"]] // التكرار على الإدخالات Object.entries(user).forEach(([key, value]) => { console.log(`${key}: ${value}`); }); // الناتج: // name: John Doe // age: 30 // email: john@example.com // role: developer
حالات الاستخدام: هذه الدوال مثالية لتحويل الكائنات إلى مصفوفات لعمليات map أو filter أو reduce.

Object.assign() لدمج الكائنات

Object.assign() ينسخ الخصائص من كائنات مصدر إلى كائن هدف:

// الاستخدام الأساسي const target = { a: 1, b: 2 }; const source1 = { b: 3, c: 4 }; const source2 = { c: 5, d: 6 }; const result = Object.assign(target, source1, source2); console.log(result); // { a: 1, b: 3, c: 5, d: 6 } console.log(target); // { a: 1, b: 3, c: 5, d: 6 } (تم تعديل الهدف!) // إنشاء كائن جديد (نمط شائع) const obj1 = { x: 1, y: 2 }; const obj2 = { y: 3, z: 4 }; const merged = Object.assign({}, obj1, obj2); console.log(merged); // { x: 1, y: 3, z: 4 } console.log(obj1); // { x: 1, y: 2 } (الأصلي دون تغيير) // استنساخ كائن (نسخة سطحية) const original = { name: "John", age: 30 }; const clone = Object.assign({}, original); console.log(clone); // { name: "John", age: 30 } // البديل الحديث: معامل النشر const modernClone = { ...original }; const modernMerged = { ...obj1, ...obj2 };
مهم: Object.assign() يُجري نسخة سطحية. الكائنات المتداخلة تُنسخ بالمرجع، وليس بالقيمة.

Object.freeze() و Object.seal()

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

// Object.freeze() - يجعل الكائن غير قابل للتغيير const frozenUser = { name: "John", age: 30 }; Object.freeze(frozenUser); frozenUser.age = 31; // يفشل بصمت (يرمي خطأ في الوضع الصارم) frozenUser.email = "john@example.com"; // لا يمكن إضافة خصائص جديدة delete frozenUser.name; // لا يمكن حذف الخصائص console.log(frozenUser); // { name: "John", age: 30 } // تحقق مما إذا كان مجمداً console.log(Object.isFrozen(frozenUser)); // true // Object.seal() - يمنع إضافة/إزالة الخصائص لكن يسمح بالتعديل const sealedUser = { name: "Jane", age: 25 }; Object.seal(sealedUser); sealedUser.age = 26; // يعمل! يمكن تعديل الخصائص الموجودة sealedUser.email = "jane@example.com"; // لا يمكن إضافة خصائص جديدة delete sealedUser.name; // لا يمكن حذف الخصائص console.log(sealedUser); // { name: "Jane", age: 26 } console.log(Object.isSealed(sealedUser)); // true
الاختلافات: Object.freeze(): - لا يمكن إضافة الخصائص - لا يمكن إزالة الخصائص - لا يمكن تعديل الخصائص - استخدم للثبات الحقيقي Object.seal(): - لا يمكن إضافة الخصائص - لا يمكن إزالة الخصائص - يمكن تعديل الخصائص الموجودة - استخدم عندما تريد بنية ثابتة لكن قيم قابلة للتغيير

Object.defineProperty() وواصفات الخصائص

عرّف أو عدّل خصائص الكائن بتحكم دقيق:

const person = {}; // عرّف خاصية مع واصفات Object.defineProperty(person, "name", { value: "John", writable: true, // يمكن تغييرها enumerable: true, // تظهر في الحلقات configurable: true // يمكن حذفها أو إعادة تهيئتها }); // عرّف خاصية للقراءة فقط Object.defineProperty(person, "id", { value: 12345, writable: false, // لا يمكن تغييرها enumerable: true, configurable: false // لا يمكن حذفها }); // عرّف خاصية مخفية Object.defineProperty(person, "password", { value: "secret123", writable: true, enumerable: false, // لن تظهر في Object.keys() أو for...in configurable: true }); console.log(person.name); // "John" console.log(person.id); // 12345 person.id = 99999; // يفشل بصمت (يرمي خطأ في الوضع الصارم) console.log(person.id); // 12345 (دون تغيير) console.log(Object.keys(person)); // ["name", "id"] (password مخفية) // احصل على واصف الخاصية const descriptor = Object.getOwnPropertyDescriptor(person, "name"); console.log(descriptor); // { value: "John", writable: true, enumerable: true, configurable: true }

Getters و Setters مع Object.defineProperty()

أنشئ خصائص محسوبة باستخدام getters و setters:

const user = { firstName: "John", lastName: "Doe" }; // عرّف getter Object.defineProperty(user, "fullName", { get() { return `${this.firstName} ${this.lastName}`; }, set(value) { const parts = value.split(" "); this.firstName = parts[0]; this.lastName = parts[1]; }, enumerable: true, configurable: true }); console.log(user.fullName); // "John Doe" user.fullName = "Jane Smith"; console.log(user.firstName); // "Jane" console.log(user.lastName); // "Smith" console.log(user.fullName); // "Jane Smith" // Getters و setters في كائنات حرفية (صيغة حديثة) const rectangle = { width: 10, height: 5, get area() { return this.width * this.height; }, set dimensions(value) { [this.width, this.height] = value.split("x").map(Number); } }; console.log(rectangle.area); // 50 rectangle.dimensions = "20x15"; console.log(rectangle.area); // 300
أفضل ممارسة: استخدم getters للخصائص المحسوبة و setters للتحقق من الصحة أو التأثيرات الجانبية عند تغيير الخصائص.

Object.create() للوراثة النموذجية

أنشئ كائنات مع نموذج أولي محدد:

// أنشئ كائن نموذج أولي const animalPrototype = { eat() { return `${this.name} is eating`; }, sleep() { return `${this.name} is sleeping`; } }; // أنشئ كائنات مع هذا النموذج الأولي const dog = Object.create(animalPrototype); dog.name = "Buddy"; dog.bark = function() { return `${this.name} says Woof!`; }; const cat = Object.create(animalPrototype); cat.name = "Whiskers"; cat.meow = function() { return `${this.name} says Meow!`; }; console.log(dog.eat()); // "Buddy is eating" console.log(dog.bark()); // "Buddy says Woof!" console.log(cat.sleep()); // "Whiskers is sleeping" console.log(cat.meow()); // "Whiskers says Meow!" // أنشئ مع الخصائص والواصفات const person = Object.create(animalPrototype, { name: { value: "John", writable: true, enumerable: true }, age: { value: 30, writable: true, enumerable: true } }); console.log(person.name); // "John" console.log(person.eat()); // "John is eating"

Object.hasOwn() و hasOwnProperty()

تحقق مما إذا كان الكائن لديه خاصية (وليست موروثة):

const parent = { parentProp: "I'm from parent" }; const child = Object.create(parent); child.childProp = "I'm from child"; // hasOwnProperty (دالة قديمة) console.log(child.hasOwnProperty("childProp")); // true console.log(child.hasOwnProperty("parentProp")); // false console.log(child.hasOwnProperty("toString")); // false // Object.hasOwn() (حديثة، أكثر أماناً) console.log(Object.hasOwn(child, "childProp")); // true console.log(Object.hasOwn(child, "parentProp")); // false // معامل "in" يتحقق من سلسلة النموذج الأولي بأكملها console.log("childProp" in child); // true console.log("parentProp" in child); // true console.log("toString" in child); // true
توصية: فضّل Object.hasOwn() على hasOwnProperty() لأنها أكثر أماناً وتعمل مع الكائنات المُنشأة عبر Object.create(null).

Object.is() لمقارنة التساوي

Object.is() يحدد ما إذا كانت قيمتان متطابقتان:

// Object.is() مقابل معامل === console.log(Object.is(25, 25)); // true console.log(Object.is("hello", "hello")); // true console.log(Object.is(true, true)); // true // حالات خاصة حيث تختلف Object.is() عن === // مقارنة NaN console.log(NaN === NaN); // false console.log(Object.is(NaN, NaN)); // true // +0 و -0 console.log(+0 === -0); // true console.log(Object.is(+0, -0)); // false // مقارنة الكائنات (كلاهما يتحقق من المرجع) const obj1 = { a: 1 }; const obj2 = { a: 1 }; const obj3 = obj1; console.log(obj1 === obj2); // false (مراجع مختلفة) console.log(Object.is(obj1, obj2)); // false console.log(obj1 === obj3); // true (نفس المرجع) console.log(Object.is(obj1, obj3)); // true

Object.fromEntries() وتحويل هياكل البيانات

حوّل مصفوفات من أزواج المفتاح-القيمة مرة أخرى إلى كائنات:

// حوّل مصفوفة من الإدخالات إلى كائن const entries = [ ["name", "John"], ["age", 30], ["role", "developer"] ]; const user = Object.fromEntries(entries); console.log(user); // { name: "John", age: 30, role: "developer" } // حوّل Map إلى كائن const map = new Map([ ["firstName", "John"], ["lastName", "Doe"], ["age", 30] ]); const objFromMap = Object.fromEntries(map); console.log(objFromMap); // { firstName: "John", lastName: "Doe", age: 30 } // استخدام عملي: تحويل قيم الكائن const prices = { apple: 1.5, banana: 0.8, orange: 2.0 }; // تطبيق خصم 20% const discountedPrices = Object.fromEntries( Object.entries(prices).map(([key, value]) => [key, value * 0.8]) ); console.log(discountedPrices); // { apple: 1.2, banana: 0.64, orange: 1.6 }

Object.getOwnPropertyNames() و Object.getOwnPropertySymbols()

احصل على جميع الخصائص، بما في ذلك غير القابلة للعد:

const obj = { visibleProp: "I'm visible" }; Object.defineProperty(obj, "hiddenProp", { value: "I'm hidden", enumerable: false }); const symbol = Symbol("symbolProp"); obj[symbol] = "I'm a symbol property"; // Object.keys() تُرجع فقط الخصائص النصية القابلة للعد console.log(Object.keys(obj)); // ["visibleProp"] // Object.getOwnPropertyNames() تُرجع جميع الخصائص النصية console.log(Object.getOwnPropertyNames(obj)); // ["visibleProp", "hiddenProp"] // Object.getOwnPropertySymbols() تُرجع خصائص الرموز console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(symbolProp)] // احصل على جميع الخصائص (النصوص والرموز) const allProps = [ ...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj) ]; console.log(allProps); // ["visibleProp", "hiddenProp", Symbol(symbolProp)]

تمرين تطبيقي:

التحدي: أنشئ كائن أدوات بالدوال التالية:

  • deepClone(obj) - ينشئ نسخة عميقة من كائن
  • pick(obj, keys) - يُرجع كائناً جديداً بالمفاتيح المحددة فقط
  • omit(obj, keys) - يُرجع كائناً جديداً بدون المفاتيح المحددة
  • isEmpty(obj) - يتحقق مما إذا كان الكائن لا يحتوي على خصائص

الحل:

const ObjectUtils = { // استنساخ عميق باستخدام JSON (بسيط لكن له قيود) deepClone(obj) { if (obj === null || typeof obj !== "object") { return obj; } if (obj instanceof Date) { return new Date(obj); } if (obj instanceof Array) { return obj.map(item => this.deepClone(item)); } const cloned = {}; for (const key in obj) { if (Object.hasOwn(obj, key)) { cloned[key] = this.deepClone(obj[key]); } } return cloned; }, // اختر خصائص محددة pick(obj, keys) { return keys.reduce((result, key) => { if (Object.hasOwn(obj, key)) { result[key] = obj[key]; } return result; }, {}); }, // احذف خصائص محددة omit(obj, keys) { return Object.fromEntries( Object.entries(obj).filter(([key]) => !keys.includes(key)) ); }, // تحقق مما إذا كان الكائن فارغاً isEmpty(obj) { return Object.keys(obj).length === 0; } }; // اختبر الأدوات const user = { id: 1, name: "John", email: "john@example.com", password: "secret123", profile: { age: 30, city: "New York" } }; // استنساخ عميق const clonedUser = ObjectUtils.deepClone(user); clonedUser.profile.age = 31; console.log(user.profile.age); // 30 (الأصلي دون تغيير) console.log(clonedUser.profile.age); // 31 // اختر const publicUser = ObjectUtils.pick(user, ["id", "name", "email"]); console.log(publicUser); // { id: 1, name: "John", email: "john@example.com" } // احذف const safeUser = ObjectUtils.omit(user, ["password"]); console.log(safeUser); // { id: 1, name: "John", email: "john@example.com", profile: {...} } // isEmpty console.log(ObjectUtils.isEmpty({})); // true console.log(ObjectUtils.isEmpty(user)); // false

الملخص

في هذا الدرس، تعلمت:

  • Object.keys() و values() و entries() للتكرار على الكائنات
  • Object.assign() لدمج الكائنات (نسخة سطحية)
  • Object.freeze() و seal() للثبات
  • Object.defineProperty() للتحكم الدقيق في الخصائص
  • Getters و setters للخصائص المحسوبة
  • Object.create() للوراثة النموذجية
  • Object.hasOwn() للتحقق من الخصائص
  • Object.is() لمقارنة التساوي
  • Object.fromEntries() لتحويل المصفوفات إلى كائنات
التالي: في الدرس التالي، سنستكشف واجهات Proxy و Reflect القوية لاعتراض وتخصيص عمليات الكائنات!