الكلمة المفتاحية this في جافاسكريبت: شرح 5 قواعد أساسية للربط للمبتدئين
تُعد الكلمة المفتاحية this في جافاسكريبت من أكثر الجوانب تحديًا في اللغة التي يصعب فهمها واستيعابها، ومع ذلك، فهي ذات أهمية بالغة لكتابة أكواد جافاسكريبت أكثر تقدمًا وفعالية. تتيح لنا الكلمة المفتاحية this في جافاسكريبت تحقيق ما يلي:
- إعادة استخدام الدوال في سياقات تنفيذ مختلفة. هذا يعني أن الدالة بمجرد تعريفها يمكن استدعاؤها لكائنات مختلفة باستخدام الكلمة المفتاحية
this. - تحديد الكائن في سياق التنفيذ الحالي عند استدعاء دالة أو ميثود.
ترتبط الكلمة المفتاحية this ارتباطًا وثيقًا بدوال جافاسكريبت. عندما يتعلق الأمر بـ this، فإن الأمر الأساسي هو فهم مكان استدعاء الدالة، لأننا لا نعرف ما تحتويه الكلمة المفتاحية this حتى يتم استدعاء الدالة فعليًا. يمكن تصنيف استخدامات this إلى خمسة جوانب ربط مختلفة. في هذا المقال، سنتعلم عن هذه الجوانب الخمسة كلها مع الأمثلة التوضيحية.
أولاً: ما هو الربط (Binding)؟
في جافاسكريبت، تُعرف Lexical Environment (البيئة المعجمية) بأنها المكان الذي يُكتب فيه الكود الخاص بك فعليًا. في المثال التالي، المتغير name موجود lexically (معجميًا) داخل الدالة sayName().
function sayName ( ) {
let name = 'someName' ;
console .log( 'The name is, ' , name);
}
يشير Execution Context (سياق التنفيذ) إلى الكود الذي يتم تشغيله حاليًا وكل ما يساعد في تشغيله. يمكن أن تكون هناك العديد من البيئات المعجمية المتاحة، ولكن البيئة التي تعمل حاليًا تتم إدارتها بواسطة سياق التنفيذ.

يحتوي كل سياق تنفيذ على Environment Record (سجل بيئي). عندما يقوم محرك جافاسكريبت بتنفيذ الكود، تتم إضافة أسماء المتغيرات والدوال إلى السجل البيئي. تُعرف هذه الظاهرة باسم Binding (الربط) في جافاسكريبت. يساعد الربط في ربط المعرفات (المتغيرات، أسماء الدوال) بالكلمة المفتاحية this لسياق تنفيذ معين. لا تقلق إذا وجدت هذا صعب الفهم الآن، ستفهم الأمر بشكل أفضل كلما تقدمنا في الشرح.
القاعدة رقم 1: كيف يعمل الربط الضمني (Implicit Binding) في جافاسكريبت
يغطي الربط الضمني معظم حالات الاستخدام للتعامل مع الكلمة المفتاحية this. عندما نستدعي ميثود (دالة تابعة لكائن) من كائن، فإننا نستخدم تدوين النقطة (.) للوصول إليها. في الربط الضمني، تحتاج إلى التحقق من الكائن المجاور للميثود وقت الاستدعاء. هذا يحدد ما الذي ترتبط به this. دعنا نلقي نظرة على مثال لفهم ذلك بشكل أفضل:
let blog = {
name : 'Tapas' ,
address : 'freecodecamp' ,
message : function ( ) {
console .log( ` ${ this .name} blogs on ${ this .address} ` );
}
};
blog.message();
هنا، ترتبط this بالكائن blog. نعرف هذا لأننا نستدعي الميثود message() على الكائن blog. لذلك، ستقوم this.name بطباعة ‘Tapas’ و this.address بطباعة ‘freeCodeCamp’ في وحدة التحكم (console).
دعنا نرى مثالاً آخر لفهم هذا المفهوم بشكل أفضل:
function greeting ( obj ) {
obj.logMessage = function ( ) {
console .log( ` ${ this .name} is ${ this .age} years old!` );
}
};
const tom = { name : 'Tom' , age : 7 };
const jerry = { name : 'jerry' , age : 3 };
greeting(tom);
greeting(jerry);
tom.logMessage ();
jerry.logMessage ();
في هذا المثال، لدينا كائنان، tom و jerry. قمنا بتزيين (تحسين) هذه الكائنات عن طريق إرفاق ميثود تسمى logMessage(). لاحظ أنه عندما نستدعي tom.logMessage()، تم استدعاؤها على الكائن tom. لذلك، ترتبط this بالكائن tom وتقوم بطباعة القيمة ‘Tom’ و ‘7’ (this.name يساوي ‘Tom’ و this.age يساوي 7 هنا). وينطبق الشيء نفسه عند استدعاء jerry.logMessage().
القاعدة رقم 2: كيف يعمل الربط الصريح (Explicit Binding) في جافاسكريبت
لقد رأينا أن جافاسكريبت تنشئ بيئة لتنفيذ الكود الذي نكتبه. إنها تهتم بإنشاء الذاكرة للمتغيرات والدوال والكائنات وما إلى ذلك في مرحلة الإنشاء (creation phase). وأخيرًا، تقوم بتنفيذ الكود في مرحلة التنفيذ (execution phase). تُسمى هذه البيئة الخاصة Execution Context (سياق التنفيذ). يمكن أن يكون هناك العديد من هذه البيئات (سياقات التنفيذ) في تطبيق جافاسكريبت. يعمل كل سياق تنفيذ بشكل مستقل عن الآخرين. ولكن في بعض الأحيان، قد نرغب في استخدام أشياء من سياق تنفيذ واحد في سياق آخر. هذا هو المكان الذي يأتي فيه الربط الصريح (explicit binding) للعب دور.
في الربط الصريح، يمكننا استدعاء دالة مع كائن عندما تكون الدالة خارج سياق تنفيذ الكائن. هناك ثلاث ميثودات خاصة جدًا، call() و apply() و bind()، تساعدنا في تحقيق الربط الصريح.
كيف تعمل ميثود call() في جافاسكريبت
مع ميثود call()، يتم تمرير السياق الذي يجب أن تُستدعى به الدالة كمعامل إلى call(). دعنا نرى كيف تعمل مع مثال:
let getName = function ( ) {
console .log( this .name);
}
let user = {
name : 'Tapas' ,
address : 'Freecodecamp'
};
getName.call(user);
هنا، تم استدعاء ميثود call() على دالة تسمى getName(). تقوم دالة getName() فقط بطباعة this.name. ولكن ما هي this هنا؟ يتم تحديد ذلك من خلال ما تم تمريره إلى ميثود call(). هنا، سترتبط this بالكائن user لأننا قمنا بتمرير user كمعامل إلى ميثود call(). لذلك، يجب أن تطبع this.name قيمة خاصية name للكائن user، وهي ‘Tapas’.
في المثال أعلاه، قمنا بتمرير وسيط واحد فقط إلى call(). ولكن يمكننا أيضًا تمرير وسائط متعددة إلى call()، مثل هذا:
let getName = function ( hobby1, hobby2 ) {
console .log( this .name + ' likes ' + hobby1 + ' , ' + hobby2);
}
let user = {
name : 'Tapas' ,
address : 'Bangalore'
};
let hobbies = [ 'Swimming' , 'Blogging' ];
getName.call(user, hobbies[ 0 ], hobbies[ 1 ]);
هنا قمنا بتمرير وسائط متعددة إلى ميثود call(). يجب أن يكون الوسيط الأول هو سياق الكائن الذي يجب استدعاء الدالة به. يمكن أن تكون المعاملات الأخرى مجرد قيم لاستخدامها. هنا أقوم بتمرير ‘Swimming’ و ‘Blogging’ كمعاملين إلى دالة getName(). هل لاحظت نقطة ضعف هنا؟ في حالة call()، يجب تمرير الوسائط واحدًا تلو الآخر – وهي ليست طريقة ذكية للقيام بالأشياء! هذا هو المكان الذي تظهر فيه ميثودنا التالية، apply().
كيف تعمل ميثود apply() في جافاسكريبت
يمكن حل هذه الطريقة الشاقة لتمرير الوسائط إلى ميثود call() بواسطة ميثود بديلة أخرى تسمى apply(). إنها مطابقة تمامًا لـ call() ولكنها تسمح لك بتمرير الوسائط بشكل أكثر ملاءمة. ألقِ نظرة:
let getName = function ( hobby1, hobby2 ) {
console .log( this .name + ' likes ' + hobby1 + ' , ' + hobby2);
}
let user = {
name : 'Tapas' ,
address : 'Bangalore'
};
let hobbies = [ 'Swimming' , 'Blogging' ];
getName.apply(user, hobbies);
هنا، نحن قادرون على تمرير مصفوفة من الوسائط، وهو أمر أكثر ملاءمة بكثير من تمريرها واحدًا تلو الآخر.
- نصيحة: عندما يكون لديك وسيط واحد فقط أو لا توجد وسائط قيم لتمريرها، استخدم
call(). عندما يكون لديك وسائط قيم متعددة لتمريرها، استخدمapply().
كيف تعمل ميثود bind() في جافاسكريبت
تتشابه ميثود bind() مع ميثود call() ولكن مع اختلاف واحد. على عكس ميثود call() التي تستدعي الدالة مباشرة، فإن bind() تُرجع دالة جديدة تمامًا ويمكننا استدعاء تلك الدالة بدلاً من ذلك.
let getName = function ( hobby1, hobby2 ) {
console .log( this .name + ' likes ' + hobby1 + ' , ' + hobby2);
}
let user = {
name : 'Tapas' ,
address : 'Bangalore'
};
let hobbies = [ 'Swimming' , 'Blogging' ];
let newFn = getName.bind(user, hobbies[ 0 ], hobbies[ 1 ]);
newFn();
هنا، لا تستدعي getName.bind() الدالة getName() مباشرة. بل تُرجع دالة جديدة، newFn، ويمكننا استدعاؤها على النحو newFn().
القاعدة رقم 3: ربط الكائن الجديد (New Binding) في جافاسكريبت
تُستخدم الكلمة المفتاحية new لإنشاء كائن من دالة المُنشئ (constructor function).
let Cartoon = function ( name, character ) {
this .name = name;
this .character = character;
this .log = function ( ) {
console .log( this .name + ' is a ' + this .character);
}
};
يمكنك إنشاء كائنات باستخدام الكلمة المفتاحية new على النحو التالي:
let tom = new Cartoon( 'Tom' , 'Cat' );
let jerry = new Cartoon( 'Jerry' , 'Mouse' );
عندما يتم استدعاء دالة باستخدام الكلمة المفتاحية new، تقوم جافاسكريبت بإنشاء كائن this داخلي (مثل this = {}) داخل الدالة. يرتبط this الذي تم إنشاؤه حديثًا بالكائن الذي يتم إنشاؤه باستخدام الكلمة المفتاحية new. هل يبدو الأمر معقدًا؟ حسنًا، دعنا نقسمه. خذ هذا السطر:
let tom = new Cartoon( 'Tom' , 'Cat' );
هنا، تم استدعاء الدالة Cartoon باستخدام الكلمة المفتاحية new. لذلك، سيتم ربط this الذي تم إنشاؤه داخليًا بالكائن الجديد الذي يتم إنشاؤه هنا، وهو tom.
القاعدة رقم 4: ربط الكائن العام (Global Object Binding) في جافاسكريبت
ماذا تتوقع أن يكون ناتج الكود أدناه؟ وما الذي ترتبط به this هنا؟
let sayName = function ( name ) {
console .log( this .name);
};
window .name = 'Tapas' ;
sayName();
إذا لم يتم حل الكلمة المفتاحية this بأي من أنواع الربط السابقة (implicit، explicit، أو new)، فإن this ترتبط بالكائن العام (window في المتصفحات). ومع ذلك، هناك استثناء واحد: وضع جافاسكريبت الصارم (strict mode) لا يسمح بهذا الربط الافتراضي.
"use strict" ;
function myFunction ( ) {
return this ;
}
في الحالة أعلاه، تكون قيمة this هي undefined.
القاعدة رقم 5: ربط عنصر حدث HTML في جافاسكريبت
في معالجات أحداث HTML (HTML event handlers)، ترتبط this بعناصر HTML التي تتلقى الحدث.
< button onclick = "console.log(this)" > Click Me! </ button >
هذا هو سجل الإخراج في وحدة التحكم (console) عند النقر فوق الزر:
"<button onclick='console.log(this)'>Click Me!</button>"
يمكنك تغيير نمط الزر باستخدام الكلمة المفتاحية this، مثل هذا:
< button onclick = "this.style.color='teal'" > Click Me! </ button >
ولكن كن حذرًا عند استدعاء دالة عند النقر على الزر واستخدام this داخل تلك الدالة.
< button onclick = "changeColor()" > Click Me! </ button >
وكود جافاسكريبت:
function changeColor ( ) {
this .style.color= 'teal' ;
}
الكود أعلاه لن يعمل كما هو متوقع. كما رأينا في القاعدة 4، هنا سترتبط this بالكائن العام (في الوضع غير الصارم 'non-strict' mode) حيث لا يوجد كائن style لتعيين اللون.
ملخص قواعد ربط this
لتلخيص ما سبق، إليك القواعد الأساسية لربط الكلمة المفتاحية this:
- الربط الضمني (Implicit Binding): ترتبط
thisبالكائن المجاور لعلامة النقطة (.) عند استدعاء الميثود. - الربط الصريح (Explicit Binding): يمكننا استدعاء دالة مع كائن عندما تكون الدالة خارج سياق تنفيذ الكائن. تلعب ميثودات
call()وapply()وbind()دورًا كبيرًا هنا. - ربط الكائن الجديد (New Binding): عندما يتم استدعاء دالة باستخدام الكلمة المفتاحية
new، ترتبط الكلمة المفتاحيةthisداخل الدالة بالكائن الجديد الذي يتم إنشاؤه. - ربط الكائن العام (Global Object Binding): عندما لا يتم حل الكلمة المفتاحية
thisبأي من أنواع الربط الأخرى (الضمني، الصريح، أو الجديد)، فإنthisترتبط بالكائن العام (windowفي المتصفحات). في وضع جافاسكريبت الصارم (strict mode)، ستكون قيمةthisهيundefined. - ربط عنصر حدث HTML (HTML Event Element Binding): في معالجات أحداث HTML، ترتبط
thisبعناصر HTML التي تتلقى الحدث.
هناك حالة أخرى تتصرف فيها this بشكل مختلف، مثل دوال الأسهم ES6 arrow functions. سنتناول ذلك في مقال مستقبلي.
الخلاصة التقنية
فهم الكلمة المفتاحية this في جافاسكريبت ليس مجرد رفاهية، بل هو ضرورة قصوى لكل مطور يسعى لكتابة كود نظيف، قابل لإعادة الاستخدام، وخالٍ من الأخطاء. إن إتقان قواعد الربط الخمسة – الضمني، الصريح، الكائن الجديد، الكائن العام، وربط عناصر HTML – يفتح الأبواب أمام فهم أعمق لكيفية عمل جافاسكريبت داخليًا، ويُمكّن من بناء تطبيقات ويب أكثر قوة ومرونة. تذكر دائمًا أن قيمة this ليست ثابتة، بل تتحدد ديناميكيًا بناءً على سياق الاستدعاء، وهو ما يجعلها أداة قوية ولكنها تتطلب فهمًا دقيقًا لتجنب السلوكيات غير المتوقعة. إن الاستثمار في فهم هذه المفاهيم الأساسية سيؤتي ثماره في رحلتك كمطور جافاسكريبت.