قد يكون معرفة قيمة 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);
};
}
هذا النمط مفيد حقًا عند استخدام طرق مثيل مثل أدوات معالجة الأحداث في المكونات (مثل مكونات التفاعل أو مكونات الويب).
قد يبدو ما سبق وكأنّه يعطّل قاعدة "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
. هل مِن أسئلة؟ هل هناك شيء فاتني؟ لا تتردد في إرسال تغريدات لي.
نشكر ماتياس بينينز وإنغفار ستيبانيان وتوماس شتاينر على التعليق.