إذا كنت تبرمج بلغة جافا سكريبت لفترة من الوقت، فستتعرف بالتأكيد على الكلمة المفتاحية لقد تسبب لك هذا بأكثر من صداع واحدوبما أن الدوال السهمية ظهرت في ES6، فقد أصبحت الأمور أكثر تعقيدًا ... أو أبسط، اعتمادًا على كيفية النظر إليها.
سنتناول في هذه المقالة كيفية عملها بمزيد من التفصيل ينطبق هذا على الدوال التقليدية ودوال الأسهم.لماذا يبدو أحيانًا أنه يشير إلى كائن معين وفي أحيان أخرى إلى الكائن العام، وفي أي الحالات يكون من المنطقي استخدام دوال الأسهم وفي أي الحالات يكون من الأفضل تجنبها؟
ماذا بالضبط this في جافا سكريبت
الكلمة المحجوزة this وهو إشارة إلى سياق التنفيذ فيما يخص الدالة التي يتم تنفيذها حاليًا. على عكس اللغات الأخرى، في جافا سكريبت، لا يعتمد القرار على مكان تعريف الدالة، بل على تنفيذها. كيفية استدعاء.
هذا يعني أنه يمكن استدعاء نفس الدالة بطرق مختلفة، وفي كل منها، this يمكن أن يشير إلى كائن مختلفليس هذا شيئًا يمكنك تغييره بتكليف مباشر (لا يمكنك فعل ذلك). this = algoلكن يمكنك التأثير عليه بآليات محددة مثل call, apply y bind.
علاوة على ذلك، يختلف سلوكهم بين الوضع الصارم والوضع غير الصارمفي الوضع غير الصارم، إذا قمت باستدعاء دالة "مجردة" (بدون كائن أمامها)، this وهو عادةً الكائن العام (في المتصفح، window)، بينما في الوضع الصارم يمكن أن يكون undefinedيُعد هذا التمييز مهماً عند مقارنة أمثلة التعليمات البرمجية من مصادر مختلفة.
هذا في السياق العالمي وفي الوظائف العادية
في المتصفحات، عندما لا تكون داخل أي وحدة أو دالة، يكون السياق العام هو الكائن windowوهناك this أشر إلى ذلك الكائنأي إذا قمت بكتابة ما يلي في وحدة التحكم:
console.log(this === window); // true en un entorno de navegador no estricto
في دالة مُعلنة بطريقة "كلاسيكية" (دالة عادية)، تكون قيمة this يعتمد ذلك على اسم تلك الوظيفة.إذا قمت باستدعائه بدون كائن مسبق، في الوضع غير الصارم this وهو عادةً ما يكون عالميًا، وبالمعنى الدقيق للكلمة سيكون undefinedلهذا السبب، في بعض الأحيان، عند نقل التعليمات البرمجية من موقع إلى آخر، لم يعد هذا ما كنت تتوقعه..
هذا في أساليب الكائنات المعرفة باستخدام الدوال العادية
عند تعريف دالة على كائن باستخدام الصيغة التقليدية، this داخل الطريقة، الإشارة إلى الكائن نفسه والتي تم استدعاء تلك الطريقة منها.
على سبيل المثال، إذا كان لديك شيء مثل:
const obj = {
speak() {
console.log(this);
}
};
obj.speak();
الدعوة obj.speak() يجعل this ضمن speak كن أنت الشخص المناسب objهذا هو السلوك الذي يتوقعه الناس عادةً بشكل بديهي: تتحدث الطريقة "نيابة عن" الكائن.
إذا استخدمت دالة كلاسيكية بدلاً من الصيغة المختصرة، فسيكون التأثير هو نفسه، لأن يكمن المفتاح في كيفية استدعاء الطريقةلا يهم ما إذا كنت قد استخدمت اختصار الطريقة أو الكلمة المفتاحية function داخل الجسم.
هذا في الطرق المعرفة باستخدام دوال السهم
تتغير الأمور عند تعريف الدالة باستخدام دالة سهمية. على سبيل المثال:
const obj2 = {
speak: () => {
console.log(this);
}
};
obj2.speak();
في هذه الحالة، عند تنفيذ obj2.speak() سترى أن this لم يعد كذلك obj2لكن السياق المعجمي الخارجي إلى ذلك الكائن، والذي يكون عادةً الكائن العام في نص برمجي متصفح كلاسيكي window.
قد يبدو هذا الأمر محيراً في المرة الأولى التي تراه فيها، لأنك تتوقع أن تشير إحدى طرق الكائن إلى الكائن نفسه. ومع ذلك، لا تقوم دوال الأسهم بإنشاء دوالها الخاصة. thisإنهم يرثون قيمة this ضمن النطاق الذي تم تعريفها فيه. إذا كان هذا النطاق عامًا، فإنها ترث النطاق العام؛ وإذا كان نطاقًا آخر، فإنها ترث ذلك النطاق الآخر.
لذلك، فإن التوصية المتكررة في الوثائق الحديثة هي: لا تستخدم دوال السهم كطرق للكائنات عندما تحتاج إلى this استهدف ذلك الهدف.
النطاق المعجمي لـ this دوال الأسهم
الفرق الرئيسي بين الدوال العادية ودوال السهم هو أن الأخيرة يوجد رابط معجمي لـ thisببساطة: إنهم لا يقررون مصيرهم this ليس عندما يتصلون ببعضهم البعض، بل عندما... خلق.
تخيل هذا المثال:
const obj3 = {
speak() {
(() => {
console.log(this);
})();
}
};
obj3.speak();
قد يبدو هنا أنه، كما هو الحال في الداخل speak نقوم بتنفيذ دالة سهمية، ينبغي أن يؤدي هذا إلى "إعادة الضبط" إلى الوضع العالميلكن يحدث العكس تمامًا: تلتقط دالة السهم this الوظيفة التي تحيط بهاوهي في هذه الحالة الطريقة speak تم استدعاؤه كـ obj3.speak()لذلك، فإن قيمة this الذي يظهر على وحدة التحكم هو الذي من obj3.
وهذا هو، لا تمتلك الدوال السهمية خصائصها الخاصة. thisبل يعيدون استخدام ما هو موجود في محيطهم المباشريُعد هذا مفيدًا للغاية في عمليات الاستدعاء المتداخلة، والمؤقتات، والوعود، وفي أي مكان آخر كنتَ فيه مضطرًا للتعامل مع الدوال التقليدية. .bind أو بحيل مثل const that = this;.
أمثلة عملية على فقدان وحفظ this
إحدى المشكلات الكلاسيكية في جافا سكريبت هي أنه عند تعريف دالة داخل طريقة، تفقد الإشارة إلى this الذي يشير إلى الكائن وينتهي بك الأمر بالنسخة العالمية أو بـ undefined.
لنأخذ الحالة النموذجية لاستخدام setTimeout ضمن طريقة لكائن ذي وظيفة تقليدية:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
setTimeout(function() {
console.log(this.nombre);
}, 3000);
}
};
persona.decirNombre(); // Muestra undefined
هنا هذا داخل الدالة التي تم تمريرها إلى setTimeout لم يعد هو الشيء personaيتم تنفيذ دالة الاستدعاء هذه في السياق العام (في المتصفح، window)، وبالتالي this.nombre يحاول البرنامج قراءة خاصية في المتغير العام، وهي خاصية غير موجودة، وينتهي به الأمر إلى أن undefined.
قبل ظهور دوال الأسهم، كان الحل الشائع هو تخزين قيمة this في متغير مساعد لسحبها إلى الدالة:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
let that = this; // aquí this es persona
setTimeout(function() {
console.log(that.nombre);
}, 3000);
}
};
بفضل هذا المتغير، يتم الحفاظ على المرجع الصحيح للكائن. لكنها حيلة غير أنيقة ومتكررة نوعًا ما. مع دوال الأسهم، تُبسط هذه المشكلة بشكل كبير:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
setTimeout(() => {
console.log(this.nombre);
}, 3000);
}
};
هنا، لا تقوم دالة السهم بإنشاء نفسها thisهكذا يرث this من الطريقة decirNombreوهو الكائن personaوالنتيجة: يتم عرض اسم "أغوستين" بشكل صحيح دون الحاجة إلى متغيرات وسيطة أو .bind.
استدعاء، تطبيق، وربط: التحكم في قيمة this
بالإضافة إلى الطريقة "الطبيعية" لضبط السياق باستخدام استدعاء دالة، توفر لنا لغة جافا سكريبت أدوات لـ فرض قيمة this في الوظائف الطبيعية: call, apply y bind.
طرق call() y apply() يقومون باستدعاء الدالة فورًا، مما يسمح لك بتمرير الكائن الذي تريد استخدامه كـ this. الفرق هو ذلك call يستقبل الحجج واحدة تلو الأخرى، بينما apply يتم استقبالها في مصفوفة. bind()بدلاً من ذلك، تُعيد دالة جديدة مع this "مرتبط" بالقيمة التي حددتهاحتى تتمكن من الاتصال بها لاحقاً عندما يناسبك ذلك.
لكن مع دوال الأسهم، لا تكون هذه الطرق مفيدة للتغيير this لأن قيمتها مرتبطة معجميًا. يمكنك استخدام call, apply o bind لتمرير الوسائط، ولكن ليس لتعديل سياق الدوال السهمية، وهو فرق مهم للغاية مع الدوال العادية.
الصيغة الأساسية لدوال السهم
ما وراء سلوك thisتوفر وظائف الأسهم بناء جملة أكثر إيجازًا وتعبيرًا في العديد من الحالات. الصيغة العامة هي:
(arg1, arg2, ..., argN) => expresion
يقوم هذا النموذج تلقائيًا بإرجاع نتيجة التعبير الموجود على يمين السهم، لذلك لا داعي لكتابة الكلمة return عندما يكون لديك تعبير بسيط واحد فقط.
بعض النقاط المشتركة في بناء الجملة:
- بدون معلمات:
() => 42س incluso_ => 42إذا كنت لا تهتم باسم الوسيط. - بمعامل واحد:
الأقواس اختيارية؛ يمكنك كتابةx => x * 2o(x) => x * 2. - مع معلمات متعددة:
الأقواس مطلوبة:(x, y) => x + y.
عندما تحتاج إلى عدة عبارات، يمكنك استخدام جسم الكتلة مع المفاتيح:
const sumar = (x, y) => {
const resultado = x + y;
return resultado;
};
في هذه الحالة، بما أن هناك مفاتيح، لم يعد هناك أي عائد ضمنيإذا لم تضع returnستُعيد الدالة undefinedينطبق هذا على كل من الدوال السهمية والدوال التقليدية.
إرجاع كائنات حرفية باستخدام دوال السهم
هناك تفصيل نحوي صغير ولكنه شائع جدًا: عندما تُرجع دالة السهم المفعول به الحرفي مباشرةيجب عليك وضعها بين قوسين حتى لا يخطئ المفسر في اعتبارها كتلة.
على سبيل المثال:
x => ({ y: x })
بدون هذه الأقواس، سيفسر جافا سكريبت الأقواس المعقوفة على أنها بداية جسم الدالة، وليس كائنًا. إنها حيلة بسيطة، لكنها تتسبب في الكثير من الأخطاء الساذجة إذا تم نسيانها.
الدوال السهمية: مجهولة المصدر وبدون نموذج أولي
دوال السهم هي مجهول التركيب النحويليس لديهم أسماء مناسبة، مما قد يعقد الأمور إلى حد ما. رسائل تصحيح الأخطاءلأنه في التتبع لا ترى معرف الدالة مباشرة، إلا إذا قمت بتعيينه لثابت باسم يمكن التعرف عليه.
بالإضافة إلى ذلك، وظائف السهم إنهم لا يملكون عقارات prototype ولا يمكن استخدامها كشركات بناءإذا حاولت استدعاءهم باستخدام newستتلقى خطأً. لإنشاء كائنات باستخدام الدوال البانية أو الفئات، لا يزال يتعين عليك استخدام الدوال أو الصيغة العادية. class.
والنتيجة الأخرى هي أن إنها غير مناسبة للأنماط التي تتطلب مرجعية داخلية ذاتية، مثل بعض أشكال الاستدعاء الذاتي المتكرر أو معالجات الأحداث التي تحتاج إلى إلغاء الاشتراك باستخدام this أو اسم الدالة نفسها.
حيث تتألق دوال الأسهم
تكمن القوة الكبيرة لدوال السهم تحديدًا في الربط المعجمي لـ thisتُعد هذه الطريقة مثالية في الحالات التي ترغب فيها في أن يحتفظ رد الاتصال الذي تمرره إلى دالة أخرى بـ this من المنطقة المحيطة.
على سبيل المثال، في كائن يحتوي على دالة تبدأ مؤقتًا وتحتاج إلى الاستمرار في الوصول إليه خصائص الكائن نفسه باستخدام this:
const contador = {
id: 42,
iniciar() {
setTimeout(() => {
console.log(this.id); // this es contador
}, 1000);
}
};
في ES5 كان من الشائع وضع .bind(this) إلى رد الاتصال أو الحفظ this في متغير آخر. باستخدام دوال السهم، يصبح الكود أكثر وضوحًا وأقرب إلى الهدف الحقيقي..
كما أنها عملية للغاية مع طرق المصفوفات مثل map, filter, reduce والشركة، لأن تقليل التشويش النحوي عندما يكون منطق الوظيفة موجزًا:
const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);
عند استخدامها باعتدال، تجعل هذه الأشكال المدمجة من السهل تتبع تدفق البيانات بنظرة سريعة.
متى يجب تجنب استخدام الدوال السهمية؟
على الرغم من أن دوال الأسهم مفيدة للغاية، إلا أنها لا تُغني عن الدوال العادية. هناك العديد من الحالات الواضحة التي من الأفضل عدم استخدامها.:
- أساليب الكائنات التي تعتمد على
this:
إذا قمت بتعريف طريقة على النحو التاليsaltos: () => { this.vidas--; }داخل كائنgato,thisلن يشير ذلك إلى القطة، بل إلى البيئة الخارجية، ولن يتم تحديث العقار كما تتوقع. - استدعاءات أحداث DOM التي تحتاج إلى
thisديناميكي:
في معالج مثلboton.addEventListener('click', () => { this.classList.toggle('on'); });,thisلن يكون الزر الذي تم الضغط عليه هو السبب، بل السياق الأعلى، الذي من المحتمل أن يسبب لك خطأ في النوع. - أدوات البناء أو الوظائف التي تحتاج
prototype:
لأنه لا يمكن استخدامه معnewلا تُعد دوال الأسهم مناسبة لإنشاء مثيلات أو للأنماط القائمة على النماذج الأولية.
في جميع هذه الحالات، أ تظل الوظيفة الطبيعية هي الأداة المناسبة لأنه يسمح بذلك this يرتبط ذلك ديناميكيًا بالطريقة التي تستدعي بها الدالة.
إذا اعتدت على الاختيار بوعي بين الوظيفة العادية ووظيفة السهم بناءً على ما تحتاجه من this ومن السياق، سيكون الكود الخاص بك أكثر قابلية للتنبؤ والقراءة. لأي شخص يحتفظ به بعد ذلك.
في نهاية المطاف، فهم قيمة this في جافا سكريبت، وكيف ترث الدوال السهمية تلك القيمة يكمن مفتاح التوقف عن المعاناة مع النتائج غير المتوقعة، والاستفادة من سهولة بناء الجملة في ES6، وكتابة الطرق، ووظائف الاستدعاء، ومعالجات الأحداث التي تفعل بالضبط ما كنت تفكر فيه، في فهم النطاق المعجمي الذي يتم إنشاؤها فيه.