فئة `Object` في جافاسكريبت

فئة `Object` في جافاسكريبت

تشرح هذه المقالة فئة Object في جافاسكريبت۔

تشرح هذه المقالة فئة Object في جافاسكريبت، بما في ذلك أمثلة عملية۔

YouTube Video

فئة Object في جافاسكريبت

Object هو كائن مدمج يُعتبر الأساس لجميع الكائنات في جافاسكريبت۔ يتم توفير العديد من الميزات الأساسية للغة، مثل إدارة الخصائص، الوراثة (سلسلة النماذج الأولية)، التعداد، الاستنساخ، والتجميد، من خلال سلوك الكائن Object

Object.create

هناك عدة طرق لإنشاء الكائنات، ويجب عليك استخدامها بشكل مناسب حسب الهدف۔

الصياغة الحرفية للكائن (الأكثر شيوعًا)

يوضح الكود التالي الطريقة الأبسط والأكثر وضوحًا لإنشاء كائن۔

 1// Create an object using object literal
 2const user = {
 3  name: "Alice",
 4  age: 30,
 5  greet() {
 6    return `Hello, I'm ${this.name}`;
 7  }
 8};
 9
10console.log(user.greet()); // "Hello, I'm Alice"
  • في هذا المثال، يتم تعريف الخصائص والطرق باستخدام القيم الحرفية۔ إنه بسيط ويوفر أداءً متفوقاً بشكل عام.۔

منشئ new Object()

نادراً ما يستخدم منشئ Object، لكن من المفيد فهم سلوكه۔

1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
  • new Object() يُرجع كائناً فارغاً، لكن الصياغة الحرفية {} أقصر وأكثر شيوعًا۔

تحديد النموذج الأولي باستخدام Object.create

يُستخدم Object.create لإنشاء كائن بنموذج أولي محدد۔

1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
  • Object.create مثالي لتصميم الكائنات القائمة على الوراثة، حيث يتيح لك التحكم الدقيق في سلسلة النماذج الأولية۔

سمات وموصفات الخصائص

يمتلك كل خاصية سمات مثل 'value' و'writable' و'enumerable' و'configurable'، والتي يمكن التحكم فيها بالتفصيل باستخدام Object.defineProperty۔

مثال أساسي لاستخدام defineProperty

التالي هو مثال لتعريف خصائص غير معدودة وخصائص للقراءة فقط باستخدام defineProperty۔

 1// Define a non-enumerable read-only property
 2const person = { name: "Carol" };
 3
 4Object.defineProperty(person, "id", {
 5  value: 12345,
 6  writable: false,
 7  enumerable: false,
 8  configurable: false
 9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
  • يتيح لك استخدام defineProperty التحكم بدقة في سلوك الخاصية مثل التعداد، وإعادة الكتابة، والحذف۔

خصائص الوصول (getter / setter)

باستخدام خصائص الوصول، يمكنك إدخال منطق عند قراءة أو كتابة الخصائص۔

 1// Use getter and setter to manage internal state
 2const data = {
 3  _value: 1,
 4  get value() {
 5    return this._value;
 6  },
 7  set value(v) {
 8    if (typeof v === "number" && v > 0) {
 9      this._value = v;
10    } else {
11      throw new Error("value must be a positive number");
12    }
13  }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
  • مع getter وsetter، يمكنك التعامل مع الوصول للخصائص كواجهة برمجة خارجية وإضافة التحقق أو تأثيرات جانبية۔

النموذج الأولي والوراثة (prototype / __proto__ / Object.getPrototypeOf)

الوراثة في جافاسكريبت تعتمد على سلسلة النموذج الأولي وليس على الفئات (الكلاسات)۔ يمكن للكائنات الرجوع إلى كائنات أخرى كنموذجها الأولي۔

Object.getPrototypeOf وObject.setPrototypeOf

يوضح المثال التالي كيفية فحص وضبط النماذج الأولية (prototypes)۔

1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
  • يقوم Object.getPrototypeOf بجلب النموذج الأولي (prototype) لكائن ما۔
  • Object.setPrototypeOf يغير النموذج الأولي لكائن موجود، لكن يجب استخدامه بحذر لأنه قد يؤثر على الأداء۔

الطرق المدمجة الهامة

سنوضح بوضوح أكثر الطرق استخداماً وأهمية المختارة من طرق النسخة التي يوفرها Object.prototype، بالإضافة إلى الطرق الساكنة التي يمتلكها Object

hasOwnProperty, isPrototypeOf, toString, valueOf

الدوال hasOwnProperty وisPrototypeOf وtoString وvalueOf تحدد السلوك الأساسي للكائنات۔

 1// Demonstrate prototype methods
 2const base = { greet() { return "hello"; } };
 3const child = Object.create(base);
 4const a = { x: 1 };
 5
 6console.log(a.hasOwnProperty("x")); // true
 7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
 8
 9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
  • hasOwnProperty هي طريقة أساسية للتحقق مما إذا كانت خاصية موجودة بشكل مباشر على الكائن نفسه۔
  • isPrototypeOf تتحقق ما إذا كان الكائن المستهدف له نفسه كنموذج أولي (prototype)۔

Object.keys, Object.values, Object.entries

Object.keys، Object.values وObject.entries ترجع قوائم بالخصائص القابلة للتعداد الخاصة بالكائن۔ هي مفيدة عند التكرار والتحويل۔

 1// Keys, values and entries
 2const item = { id: 1, name: "Widget", price: 9.99 };
 3
 4// ["id", "name", "price"]
 5console.log(Object.keys(item));
 6
 7// [1, "Widget", 9.99]
 8console.log(Object.values(item));
 9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));
  • تُستخدم هذه الطرق بشكل متكرر في تكرار وتحويل الكائنات۔

Object.assign

يُستخدم Object.assign للنسخ السطحي والدمج۔ لاحظ أن النماذج الأولية وخصائص الوصول لا يتم نسخها۔

1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
  • بالنسبة للكائنات المتداخلة، يتم نسخ المراجع فقط، لذا ستحتاج إلى تطبيق مختلف من أجل النسخ العميق۔

Object.freeze, Object.seal, Object.preventExtensions

Object.freeze وObject.seal وObject.preventExtensions تتحكم في قابلية الكائنات للتغيير۔

 1// Freeze vs seal vs preventExtensions
 2const obj = { a: 1 };
 3Object.freeze(obj);
 4obj.a = 2; // fails silently or throws in strict mode
 5delete obj.a; // fails
 6
 7const obj2 = { b: 2 };
 8Object.seal(obj2);
 9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
  • freeze هو الأكثر صرامة؛ يمنع أي تغييرات على خصائص الكائن۔
  • seal يمنع إضافة أو حذف الخصائص، لكنه يسمح بتغيير قيم الخصائص الموجودة۔
  • preventExtensions يمنع فقط إضافة خصائص جديدة؛ أما الخصائص الحالية فيمكن تغييرها أو حذفها۔

تعداد الكائن، الترتيب، وfor...in / for...of

for...in يعدد أسماء الخصائص القابلة للتعداد، لكنه أيضًا يشمل خصائص سلسلة النموذج الأولي، لذلك غالبًا ما يُستخدم مع hasOwnProperty۔ استخدام Object.keys() مع for...of أكثر أماناً ويجعل نواياك أوضح.۔

 1// Safe enumeration
 2const obj = Object.create({ inherited: true });
 3obj.own = 1;
 4
 5for (const key in obj) {
 6  if (obj.hasOwnProperty(key)) {
 7    console.log("own prop:", key);
 8  } else {
 9    console.log("inherited prop:", key);
10  }
11}
12
13for (const key of Object.keys(obj)) {
14  console.log("key via Object.keys:", key);
15}
  • قواعد تعداد الخصائص محددة في مواصفة ECMAScript، وهناك حالات يكون فيها ترتيب التعداد مضمونًا وحالات أخرى لا يكون كذلك۔ كقاعدة عامة، يتم ترتيب المفاتيح التي تفسر كأرقام بترتيب تصاعدي، بينما تتبع المفاتيح الأخرى ترتيب الإدراج.۔

الاستنساخ والنسخ العميق

هناك نوعان من نسخ الكائنات: النسخ الضحل باستخدام Object.assign أو بناء الجملة الانتشاري (spread syntax)، والنسخ العميق باستخدام الطرق العودية. من المهم استخدام كل منهما بشكل مناسب حسب الموقف.۔

النسخ السطحي (الصياغة الموسعة / Object.assign)

1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
  • عند النسخ السطحي، الكائنات المتداخلة تشارك المراجع، لذلك أي تغيير في الكائن الأصلي قد يؤثر على النسخة۔

نسخ عميق بسيط (مع ملاحظات مهمة)

استخدام حيلة JSON طريقة سريعة للنسخ العميق، لكنها تحتوي على عيوب مثل فقدان الدوال، كائنات Date، المراجع الدائرية، وقيم undefined۔ للحصول على نسخة عميقة حقيقية، عليك استخدام مكتبة متخصصة۔

1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
  • الطرق المعتمدة على JSON مناسبة لمعالجة البيانات البسيطة بسرعة، لكن في الحالات الشائعة قد لا تعمل كما هو متوقع.۔

الخلطات (Mixins) وتركيب الكائنات

بدلاً من الوراثة المتعددة، غالبًا ما يُستخدم نمط تركيب السلوكيات عبر الخلطات (Mixins)۔

 1// Simple mixin function
 2const canEat = {
 3  eat() { return `${this.name} eats`; }
 4};
 5const canWalk = {
 6  walk() { return `${this.name} walks`; }
 7};
 8
 9function createPerson(name) {
10  const person = { name };
11  return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
  • الخلطات مرنة، لكن يجب الحذر من اصطدام أسماء الخصائص وقابلية الاختبار۔

الأخطاء الشائعة وأفضل الممارسات

فيما يلي بعض الأخطاء الشائعة وأفضل الممارسات۔

  • قابلية التغيير الكائنات قابلة للتغيير افتراضيًا۔ في التطبيقات التي تستخدم إدارة الحالة، ضع في اعتبارك هياكل بيانات غير قابلة للتغيير باستخدام Object.freeze أو مكتبة غير قابلة للتغيير۔

  • تلويث النموذج الأولي (Prototype pollution) دمج بيانات خارجية مباشرة في كائن باستخدام Object.assign أو الحلقات قد يسبب تأثيرات جانبية غير متوقعة مع خصائص خاصة مثل __proto__ أو constructor، مما يشكل مخاطر أمنية۔ قم بفلترة مدخلات المستخدم قبل الدمج المباشر۔

  • مشكلات في استخدام for...in for...in يعدد خصائص النموذج الأولي أيضاً، لذا تحقق دائماً باستخدام hasOwnProperty۔ استخدام Object.keys أكثر وضوحًا۔

  • سوء استخدام النسخ السطحي فكر إذا كنت بحاجة إلى نسخ عميق لمنع تغييرات الكائنات المتداخلة من التأثير على الكائن الأصلي۔

مثال عملي: نمط تحديث الكائنات غير القابلة للتغيير

يتم استخدام الأنماط التي تُرجع كائناً جديدًا دون تعديل الحالة مباشرة في React ومكتبات مشابهة۔

 1// Immutable update example
 2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
 3
 4// Toggle todo done immutably
 5function toggleTodo(state, todoId) {
 6  return {
 7    ...state,
 8    todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
 9  };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
  • هذا الكود مثال على إنشاء كائن حالة جديد دون تعديل كائن state الأصلي بشكل مباشر۔ دالة toggleTodo تقوم بنسخ مصفوفة todos وتعيد كائنًا جديدًا بحيث يتم تعديل العنصر المستهدف فقط، وبالتالي يبقى كائن state الأصلي دون تغيير۔
  • التحديثات غير القابلة للتغيير تقلل الآثار الجانبية وتُسهل إدارة الحالة۔

مثال عملي: الدمج الآمن (انتبه لتلويث النموذج الأولي)

عند دمج JSON خارجي، تجاهل خاصية __proto__ لمنع تلويث النموذج الأولي۔

 1// Safe merge ignoring __proto__ keys
 2function safeMerge(target, source) {
 3  for (const key of Object.keys(source)) {
 4    if (key === "__proto__" || key === "constructor") continue;
 5    target[key] = source[key];
 6  }
 7  return target;
 8}
 9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
  • هذا النوع من الحماية مهم أيضًا في المكتبات والأطر البرمجية۔

اعتبارات الأداء

فيما يخص الأداء، يرجى النظر في النقاط التالية:۔

  • تجنب التغيير المتكرر للنموذج الأولي (Object.setPrototypeOf) أو الإضافة/الإزالة الديناميكية للخصائص، لأن ذلك يعوق تحسينات محرك التنفيذ۔
  • عند إنشاء العديد من الكائنات الصغيرة، تصبح عملية التحسين أكثر فعالية إذا استخدمت كائنات ذات بنية موحدة (نفس مجموعة الخصائص).۔
  • النسخ العميق مكلف حسابيًا۔ قلل من استخدامها أو فكر في استخدام التحديثات المعتمدة على الفروقات۔

الملخص

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

يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔

YouTube Video