ثمّة مشكلة معروفة تواجهنا متى مرّرنا توابِع الكائنات على أنّها ردود نداء (كما نفعل مع setTimeout)، هي ضياع هويّة الأنا this.
سنرى في هذا الفصل طرائق إصلاح هذه المشكلة.
ضياع الأنا (الكلمة المفتاحية this)
رأينا قبل الآن أمثلة كيف ضاعت قيمة this. فما نلبث أن مرّرنا التابِع إلى مكان آخر منفصلًا عن كائنه، ضاع this.
إليك ظواهر هذه المشكلة باستعمال setTimeout مثلًا:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
كما رأينا في ناتج الشيفرة، لم نرحّب بالأخ «John» (كما أردنا باستعمال this.firstName)، بل بالأخ غير المعرّف undefined!
هذا لأنّ التابِع setTimeout استلم الدالة user.sayHi منفصلةً عن كائنها. يمكن أن نكتب السطر الأخير هكذا:
let f = user.sayHi;
setTimeout(f, 1000); // ضاع سياق المستخدم user
بالمناسبة فالتابِع setTimeout داخل المتصفّحات يختلف قليلًا، إذ يضبط this=window حين نستدعي الدالة (بينما في Node.js يصير this هو ذاته كائن المؤقّت، ولكنّ هذا ليس بالأمر المهم الآن). يعني ذلك بأنّ this.firstName هنا هي فعليًا window.firstName، وهذا المتغير غير موجود. عادةً ما تصير this غير معرّفة undefined في الحالات الأخرى.
كثيرًا ما نواجه هذه المسألة ونحن نكتب الشيفرة: نريد أن نمرّر تابِع الدالة إلى مكان آخر (مثل هنا، مرّرناه للمُجدول) حيث سيُستدعى من هناك. كيف لنا أن نتأكّد بأن يُستدعى في سياقه الصحيح؟
الحل رقم واحد: نستعمل دالة مغلفة
أسهل الحلول هو استعمال دالة غالِفة Wrapping function:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
الآن اكتملت المهمة إذ استلمنا المستخدم user من البيئة المُعجمية الخارجية، وثمّ استدعينا التابِع كما العادة.
إليك ذات المهمة بأسطر أقل:
setTimeout(() => user.sayHi(), 1000); // Hello, John!
ممتازة جدًا، ولكن ستظهر لنا نقطة ضعف في بنية الشيفرة.
ماذا لو حدث وتغيّرت قيمة user قبل أن تعمل setTimeout؟ (لا تنسَ التأخير، ثانية كاملة!) حينها سنجد أنّا استدعينا الكائن الخطأ دون أن ندري!
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
// ...تغيّرت قيمة user خلال تلك الثانية
user = {
sayHi() { alert("Another user in setTimeout!"); }
};
// setTimeout! هناك مستخدم آخر داخل التابِع
الحل الثاني سيضمن لنا ألّا تحدث هكذا أمور غير متوقّعة.
الحل رقم اثنين: ربطة
تقدّم لنا الدوال تابِعًا مضمّنًا في اللغة باسم bind يتيح لنا ضبط قيمة this.
إليك صياغته الأساسية:
// ستأتي الصياغة المعقّدة لاحقًا لا تقلق
let boundFunc = func.bind(context);
ناتِج التابِع func.bind(context) هو «كائن دخيل» يشبه الدالة ويمكن لنا استدعائه على أنّه دالة، وسيمرّر هذا الاستدعاء إلى