نُنشِئ الكائنات باستخدام الصيغة الاعتيادية المختصرة {...}
. لكننا نحتاج لإنشاء العديد من الكائنات المتشابهة غالبًا، مثل العديد من
المستخدمين، أو عناصر لقائمة وهكذا. يمكن القيام بذلك باستخدام الدوال البانية (constructor functions) لكائن والمُعامِل
"new"
.
الدالة البانية
تقنيًا، الدوال البانية هي دوال عادية، لكن يوجد فكرتين متفق عليها:
- أنها تبدأ بأحرف كبيرة.
- يجب تنفيذها مع المُعامِل
"new"
فقط. إليك المثال التالي:
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
عند تنفيذ دالة مع الُعامِل new
، تُنَفَّذ الخطوات التالية:
- يُنشَأ كائن فارغ ويُسنَد إلى
this
. - يُنَفَّذ محتوى الدالة. تقوم غالبًا بتعديل
this
، وإضافة خاصيات إليه. - تُرجَع قيمة
this
. بمعنى آخر، تقومnew User(...)
بشيء يشبه ما يلي:
function User(name) {
// this = {}; (implicitly)
// this إضافة خاصيات إلى
this.name = name;
this.isAdmin = false;
// this; (implicitly) إرجاع
}
إذًا، تُعطي let user = new User("Jack")
النتيجة التالية ذاتها:
let user = {
name: "Jack",
isAdmin: false
};
الآن، إن أردنا إنشاء مستخدمين آخرين، يمكننا استدعاء new User("Ann")
، و new User("Alice")
وهكذا. تعدُّ هذه
الطريقة في بناء الكائنات أقصر من الطريقة الاعتيادية عبر الأقواس فقط، وأيضًا أسهل للقراءة. هذا هو الغرض الرئيس للبانيات،
وهي تطبيق شيفرة قابلة لإعادة الاستخدام لإنشاء الكائنات.
لاحظ أنَّه يمكن استخدام أي دالة لتكون دالة بانية تقنيًا. يعني أنه يمكن تنفيذ أي دالة مع new
، وستُنَفَّذ باستخدام الخوارزمية أعلاه.
استخدام الأحرف الكبيرة في البداية هو اتفاق شائع لتمييز الدالة البانية من غيرها وأنَّه يجب استدعاؤها مع new
.
**new function() { … }**
إن كان لدينا العديد من الأسطر البرمجية، وجميعها عن إنشاء كائن واحد مُعَقَّد، فبإمكاننا تضمينها في دالة بانية، هكذا:
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ...شيفرة إضافية لإنشاء مستخدم
// ربما منطق معقد أو أي جمل
// متغيرات محلية وهكذا..
};
لا يمكن استدعاء المُنشِئ مجددًا، لأنه غير محفوظ في أي مكان، يُنشَأ ويُستدعى فقط. لذا فإن الخدعة تهدف إلى تضمين الشيفرة التي تُنشِئ كائنًا واحدًا، دون إعادة الاستخدام وتكرار العملية مستقبلًا.
وضع اختبار الباني: new.target
ميزة متقدمة: تُستخدم الصيغة في هذا الجزء نادرًا، ويمكنك تخطيها إلا إن كنت تُريد الإلمام بكل شيء.
يمكننا فحص ما إن كانت الدالة قد استدعيت باستخدام new
أو دونه من داخل الدالة، وذلك باستخدام الخاصية الخاصة new.target
.
تكون الخاصية فارغة في الاستدعاءات العادية، وتساوي الدالة البانية إذا استُدعِيَت باستخدام new
:
Inside a function, we can check whether it was called with `new` or without it, using a special `new.target` property.
It is undefined for regular calls and equals the function if called with `new`:
```js run
function User() {
alert(new.target);
}
// "new" بدون:
User(); // undefined
// باستخدام "new":
new User(); // function User { ... }
يمكن استخدام ذلك بداخل الدالة لمعرفة إن استُدعِيَت مع new
، “في وضع بناء كائن”، أو دونه “في الوضع العادي”.
يمكننا أيضًا جعل كلًا من الاستدعاء العادي وnew
ينفِّذان الأمر ذاته -بناء كائن- هكذا:
function User(name) {
if (!new.target) { // new إن كنت تعمل بدون
return new User(name); // new ...سأضيف
}
this.name = name;
}
let john = User("John"); // new User تُوَجِّه الاستدعاء إلى
alert(john.name); // John
يستخدم هذا الأسلوب في بعض المكتبات أحيانًا لجعل الصيغة أكثر مرونة حتى يتمكن الأشخاص من استدعاء الدالة مع new
أو دونه، وتظل تعمل.
ربما ليس من الجيد استخدام ذلك في كل مكان، لأن حذف new
يجعل ما يحدث أقل وضوحًا. لكن مع new
، يعلم الجميع أن كائنًا جديدًا قد أُنشِئ.
ما تُرجِعه الدوال البانية
لا تملك الدوال البانية عادةً التعليمة return
. فَمُهِمَتُهَا هي كتابة الأمور المهمة إلى this
، وتصبح تلقائيًا هي النتيجة. لكن إن كان هناك التعليمة return
فإن القاعدة سهلة:
- إن استُدعِيَت
return
مع كائن، يُرجَع الكائن بدلًا منthis
. - إن استُدعِيَت
return
مع متغير أولي، يُتَجاهَل. بمعنىً آخر،return
مع كائن يُرجَع الكائن، وفي الحالات الأخرى تُرجَعthis
. مثلًا، يعاد في المثال التالي الكائن المرفق بعدreturn
ويهمل الكائن المسنَد إلىthis
:
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- تُرجِع هذا الكائن
}
alert( new BigUser().name ); // Godzilla, حصلنا على الكائن
وهنا مثال على استعمال return
فارغة (أو يمكننا وضع متغير أولي بعدها، لا فرق):
function SmallUser() {
this.name = "John";
return; // ← this تُرجِع
}
alert( new SmallUser().name ); // John
لا تحتوي الدوال البانية غالبًا على تعليمة الإعادة return
. نذكر هنا هذا التصرف الخاص عند إرجاع الكائنات بغرض شمول جميع النواحي.
حذف الأقواس
بالمناسبة، يمكننا حذف أقواس new
في حال غياب المعاملات مُعامِلات:
let user = new User; // <-- لا يوجد أقوس
// الغرض ذاته
let user = new User();
لا يُعد حذف الأقواس أسلوبًا جيدَا، لكن الصيغة مسموح بها من خلال المواصفات.
الدوال في الباني
استخدام الدوال البانية لإنشاء الكائنات يُعطي مرونة كبيرة. قد تحتوي الدالة البانية على مُعامِلات ترشد في بناء الكائن ووضعه، إذ يمكننا إضافة خاصيات ودوال إلى this
بالطبع. مثلًا، تُنشِئ new User(name)
في الأسفل كائنًا بالاسم المُعطَى name
والدالة sayHi
:
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
لإنشاء كائنات أكثر تعقيدًا، يوجد صيغة أكثر تقدمًا، الفئات، والتي سنغطيها لاحقًا.
الخلاصة
- الدوال البانية، أو باختصار البانيات، هي دوال عادية، لكن يوجد اتفاق متعارف عليه ببدء اسمها بحرف كبير.
- يجب استدعاء الدوال البانية باستخدام
new
فقط. يتضمن هذا الاستدعاء إنشاء كائن فارغ وإسناده إلىthis
وبدء العملية ثم إرجاع هذا الكائن في نهاية المطاف. - يمكننا استخدام الدوال البانية لإنشاء كائنات متعددة متشابهة.
- تزود JavaScript دوالًا بانية للعديد من الأنواع (الكائنات) المدمجة في اللغة: مثل النوع
Date
للتواريخ، وSet
للمجموعات وغيرها من الكائنات التي نخطط لدراستها.
عودة قريبة
غطينا الأساسيات فقط عن الكائنات وبانياتها في هذا الفصل. هذه الأساسيات مهمة تمهيدًا لتعلم المزيد عن أنواع البيانات والدوال في الفصل التالي. بعد تعلم ذلك، سنعود للكائنات ونغطيها بعمق في فصل الخصائص، والوراثة، والأصناف.
تمارين
دالتين – كائن واحد
الأهمية: 2
هل يمكن إنشاء الدالة A
و B
هكذا new A()==new B()
؟
function A() { ... }
function B() { ... }
let a = new A;
let b = new B;
alert( a == b ); // true
إن كان ممكنًا، وضح ذلك بمثال برمجي.
الحل
نعم يمكن ذلك.
إن كان هناك دالة تُرجِع كائنًا فإن new
تُرجِعه بدلًا من this
. لذا فمن الممكن، مثلًا، إرجاع الكائن المعرف خارجيًا obj
:
let obj = {};
function A() { return obj; }
function B() { return obj; }
alert( new A() == new B() ); // true
إنشاء حاسبة جديدة
الأهمية: 5
إنشِئ دالة بانية باسم Calculator
تنشئ كائنًا بثلاث دوال:
read()
تطلب قيمتين باستخدام سطر الأوامر وتحفظها في خاصيات الكائن.sum()
تُرجِع مجموع الخاصيتين.mul()
تُرجِع حاصل ضرب الخاصيتين. مثلًا:
let calculator = new Calculator();
calculator.read();
alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );
الحل
function Calculator() {
this.read = function() {
this.a = +prompt('a?', 0);
this.b = +prompt('b?', 0);
};
this.sum = function() {
return this.a + this.b;
};
this.mul = function() {
return this.a * this.b;
};
}
let calculator = new Calculator();
calculator.read();
alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );
إنشاء مجمِّع
الأهمية: 5
أنشِئ دالة بانية باسم Accumulator(startingValue)
، إذ يجب أن يتصف هذا الكائن بأنَّه:
- يخزن القيمة الحالية في الخاصية
value
. تُعَيَّن قيمة البدء عبر المعاملstartingValue
المعطى من الدالة البانية. - يجب أن تستخدم الدالة
read()
الدالةprompt
لقراءة رقم جديد وإضافته إلىvalue
. بمعنى آخر، الخاصيةvalue
هي مجموع القيم المدخلة بواسطة المستخدم بالإضافة إلى القيمة الأوليةstartingValue
. هنا مثال على ما يجب أن يُنَفَّذ:
let accumulator = new Accumulator(1); // القيمة الأولية 1
accumulator.read(); // يضيف قيمة مدخلة بواسطة المستخدم
accumulator.read(); // يضيف قيمة مدخلة بواسطة المستخدم
alert(accumulator.value); // يعرض مجموع القيم
الحل
function Accumulator(startingValue) {
this.value = startingValue;
this.read = function() {
this.value += +prompt('How much to add', 0);
};
}
let accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert(accumulator.value);
ترجمة -وبتصرف- للفصل Constructor, operator “new” من كتاب The JavaScript Language