فئة `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...infor...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 أيضًا.۔