قد يكون من الصعب معرفة قيمة this
في JavaScript، إليك كيفية إجراء ذلك:
إنّ this
في JavaScript هو موضوع العديد من النكات، ويعود السبب في ذلك إلى أنّه معقّد جدًا.
ومع ذلك، رأيت مطوّرين ينفّذون إجراءات أكثر تعقيدًا وخصوصية للنطاق لتجنّب التعامل
مع هذا this
. إذا لم تكن متأكدًا من this
، نأمل أن تكون هذه المعلومات مفيدة. هذا هو دليلي حول this
.
سأبدأ بالحالة الأكثر تحديدًا، وسأنتهي بالحالة الأقل تحديدًا. هذه المقالة
تشبه إلى حدٍ ما if (…) … else if () … else if (…) …
كبيرًا، لذا يمكنك الانتقال مباشرةً إلى
القسم الأول الذي يتطابق مع الرمز الذي تبحث عنه.
- إذا تم تعريف الدالة على أنّها دالة سهم
- بخلاف ذلك، إذا تم استدعاء الدالة/الفئة باستخدام
new
- بخلاف ذلك، إذا كانت الدالة تحتوي على قيمة
this
"محدودة" - بخلاف ذلك، في حال ضبط
this
في وقت المكالمة - بخلاف ذلك، إذا تم استدعاء الدالة من خلال عنصر رئيسي (
parent.func()
) - وإلا، إذا كانت الدالة أو النطاق الرئيسي في الوضع الصارم
- غير ذلك
إذا تم تعريف الدالة على أنّها دالة سهمية:
const arrowFunction = () => {
console.log(this);
};
في هذه الحالة، تكون قيمة this
دائمًا مساوية لقيمة this
في النطاق الرئيسي:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
إنّ دوالّ الأسهم رائعة لأنّه لا يمكن تغيير القيمة الداخلية لـ this
، بل هي دائمًا متطابقة
مع القيمة الخارجية لـ this
.
أمثلة أخرى
باستخدام دوالّ الأسهم، لا يمكن تغيير قيمة this
باستخدام bind
:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
باستخدام دوالّ الأسهم، لا يمكن تغيير قيمة this
باستخدام call
أو apply
:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
باستخدام دوالّ الأسهم، لا يمكن تغيير قيمة this
من خلال استدعاء الدالة كعضو في
عنصر آخر:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
باستخدام دوالّ الأسهم، لا يمكن تغيير قيمة this
من خلال استدعاء الدالة كدالة
لإنشاء عنصر:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
طرق المثيل "المرتبطة"
باستخدام طرق المثيل، إذا كنت تريد التأكّد من أنّ this
تشير دائمًا إلى مثيل الفئة، فإنّ أفضل
طريقة هي استخدام دوالّ الأسهم وحقول
الفئة:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
يكون هذا النمط مفيدًا جدًا عند استخدام طرق المثيل كمستمعين للأحداث في المكوّنات (مثل مكوّنات React أو مكوّنات الويب).
قد يبدو لك أنّ الرمز أعلاه يخالف القاعدة "this
ستكون متطابقة مع this
في نطاق العنصر الرئيسي"، ولكنه يصبح منطقيًا إذا اعتبرت حقول الفئة حلاًّ نحويًا لضبط العناصر في الدالة المُنشئة:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
تتضمن الأنماط البديلة ربط دالة حالية في الدالة الانشائية، أو تعيين الدالة في الدالة الانشائية. إذا لم تتمكّن من استخدام حقول الفئة لسبب ما، يُعدّ تعيين الدوالّ في المنشئ بديلاً معقولاً:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
بخلاف ذلك، إذا تمّ استدعاء الدالة/الفئة باستخدام new
:
new Whatever();
سيؤدي الرمز البرمجي أعلاه إلى استدعاء Whatever
(أو دالة الإنشاء الخاصة بها إذا كانت فئة) مع ضبط this
على
نتيجة Object.create(Whatever.prototype)
.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
وينطبق الأمر نفسه على أدوات الإنشاء القديمة:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
أمثلة أخرى
عند استدعاء new
، لا يمكن تغيير قيمة this
باستخدام bind
:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
عند استدعاء الدالة باستخدام new
، لا يمكن تغيير قيمة this
من خلال استدعاء الدالة كعضو
في عنصر آخر:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
بخلاف ذلك، إذا كانت الدالة تحتوي على قيمة this
"محدودة":
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
عند استدعاء boundFunction
، ستكون قيمة this
هي العنصر الذي يتم تمريره إلى bind
(boundObject
).
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
أمثلة أخرى
عند استدعاء دالة مقيّدة، لا يمكن تغيير قيمة this
باستخدام call
أو
apply
:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
عند استدعاء دالة مرتبطة، لا يمكن تغيير قيمة this
من خلال استدعاء الدالة كأحد
العناصر في عنصر آخر:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
بخلاف ذلك، في حال ضبط this
في وقت المكالمة:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
قيمة this
هي العنصر الذي تم تمريره إلى call
/apply
.
يتم ضبط this
على قيمة أخرى من خلال عناصر مثل مستمعي أحداث DOM، ويمكن أن يؤدي استخدامها
إلى إنشاء رمز برمجي يصعب فهمه:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
أتجنب استخدام this
في الحالات الموضّحة أعلاه، وأستخدِم بدلاً من ذلك:
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); });
بخلاف ذلك، إذا تم استدعاء الدالة من خلال عنصر رئيسي (parent.func()
):
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
في هذه الحالة، يتمّ استدعاء الدالة كعضو في obj
، لذا سيكون this
هو obj
. يحدث ذلك في
وقت الاستدعاء، لذا يتم قطع الرابط إذا تم استدعاء الدالة بدون العنصر الرئيسي لها أو باستخدام
عنصر رئيسي مختلف:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
القيمة someMethod() === obj
خاطئة لأنّ someMethod
لم يتم استدعاؤه كعضو في obj
. قد تكون
واجهت هذه المشكلة عند محاولة إجراء ما يلي:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
ويؤدي ذلك إلى حدوث خلل لأنّ تنفيذ querySelector
يفحص قيمة this
الخاصة به ويتوقّع
أن تكون عقدة DOM من أنواع معيّنة، ويؤدي ما سبق إلى إيقاف هذا الربط. لتحقيق ما سبق بشكل صحيح:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
حقيقة ممتعة: لا تستخدم بعض واجهات برمجة التطبيقات this
داخليًا. تم تغيير طرق وحدة التحكّم، مثل console.log
، لتجنّب مراجع this
، وبالتالي لا يلزم ربط log
بـ console
.
بخلاف ذلك، إذا كانت الدالة أو النطاق الرئيسي في الوضع الصارم:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
في هذه الحالة، تكون قيمة this
غير محدّدة. لا تكون 'use strict'
مطلوبة في الدالة إذا كان
النطاق الرئيسي في وضع
صارم (وكانت كل
الوحدات في الوضع الصارم).
غير ذلك:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
في هذه الحالة، تكون قيمة this
هي نفسها قيمة globalThis
.
أخيرًا!
هذا كل ما في الأمر! هذا كل ما أعرفه عن this
. هل مِن أسئلة؟ هل هناك شيء فاتني؟ يمكنك
مراسلتي على Twitter.
نشكر ماتياس بينينز وإنغفار ستيبانيان وتوماس شتاينر على مراجعتهم.