JavaScript المتقدم (ES6+)

الكلمة الأساسية 'this'

13 دقيقة الدرس 13 من 40

الكلمة الأساسية 'this'

الكلمة الأساسية this هي واحدة من أكثر الميزات إرباكاً ولكن قوة في JavaScript. فهم كيفية عمل this ضروري لإتقان JavaScript، خاصة عند العمل مع الكائنات والفئات والمستدعيات. في هذا الدرس، سنزيل الغموض عن this ونتعلم كيفية التحكم في قيمتها.

ما هي 'this'؟

في JavaScript، this هي كلمة أساسية خاصة تشير إلى السياق الذي يتم فيه تنفيذ الدالة. يتم تحديد قيمة this بواسطة كيفية استدعاء الدالة، وليس أين تم تعريفها.

المفهوم الأساسي: this ليست متغيراً - إنها كلمة أساسية. يتم تعيين قيمتها تلقائياً بواسطة JavaScript بناءً على سياق التنفيذ.

'this' في النطاق العام

في النطاق العام (خارج أي دالة)، تشير this إلى الكائن العام:

console.log(this); // في المتصفح: كائن Window // في Node.js: كائن global // إنشاء متغير عام this.globalVar = "أنا عام"; console.log(window.globalVar); // "أنا عام" (في المتصفح) // دالة عامة function globalFunction() { console.log(this); // كائن Window (في الوضع غير الصارم) } globalFunction();
مهم: في الوضع الصارم ('use strict')، تكون this في الدوال العامة undefined، وليس الكائن العام. هذا يمنع إنشاء متغيرات عامة عن طريق الخطأ.

'this' في طرق الكائن

عندما يتم استدعاء دالة كطريقة لكائن، تشير this إلى ذلك الكائن:

const person = { name: "أحمد", age: 30, greet: function() { console.log(`مرحباً، أنا ${this.name} وعمري ${this.age} سنة`); }, birthday: function() { this.age++; console.log(`عيد ميلاد سعيد! الآن عمري ${this.age}`); } }; person.greet(); // مرحباً، أنا أحمد وعمري 30 سنة person.birthday(); // عيد ميلاد سعيد! الآن عمري 31 // 'this' تشير إلى كائن person console.log(person.age); // 31

فقدان سياق 'this'

مشكلة شائعة تحدث عندما تفقد سياق this:

const person = { name: "أحمد", greet: function() { console.log(`مرحباً، أنا ${this.name}`); } }; person.greet(); // مرحباً، أنا أحمد // المشكلة: فقدان السياق عند التعيين لمتغير const greetFunction = person.greet; greetFunction(); // مرحباً، أنا undefined (this هو Window/undefined) // المشكلة: فقدان السياق في المستدعيات setTimeout(person.greet, 1000); // مرحباً، أنا undefined

يحدث هذا لأنه عندما تشير إلى person.greet بدون استدعائها فوراً، فإنك تفقد سياق الكائن. يتم استدعاء الدالة في سياق مختلف حيث لا تشير this إلى person.

'this' في دوال السهم

دوال السهم ليس لديها this خاصة بها. إنها ترث this من النطاق المعجمي المحيط:

const person = { name: "أحمد", hobbies: ["القراءة", "البرمجة", "الألعاب"], // دالة عادية showHobbiesRegular: function() { this.hobbies.forEach(function(hobby) { // المشكلة: 'this' غير معرّفة هنا console.log(`${this.name} يحب ${hobby}`); }); }, // حل دالة السهم showHobbiesArrow: function() { this.hobbies.forEach((hobby) => { // 'this' تشير إلى كائن person console.log(`${this.name} يحب ${hobby}`); }); } }; person.showHobbiesRegular(); // undefined يحب القراءة (خطأ) person.showHobbiesArrow(); // أحمد يحب القراءة، أحمد يحب البرمجة...
أفضل ممارسة: استخدم دوال السهم للمستدعيات عندما تريد الحفاظ على سياق this الخارجي. استخدم الدوال العادية عندما تحتاج إلى this ديناميكية تعتمد على كيفية استدعاء الدالة.

طريقة call()

تتيح لك طريقة call() تعيين قيمة this بشكل صريح واستدعاء الدالة فوراً:

function introduce(greeting, punctuation) { console.log(`${greeting}، أنا ${this.name}${punctuation}`); } const person1 = { name: "أحمد" }; const person2 = { name: "سارة" }; // استدعاء introduce مع person1 كـ 'this' introduce.call(person1, "مرحباً", "!"); // مرحباً، أنا أحمد! // استدعاء introduce مع person2 كـ 'this' introduce.call(person2, "أهلاً", "."); // أهلاً، أنا سارة. // استخدام call() لاستعارة الطرق const numbers = { data: [1, 2, 3, 4, 5] }; const stats = { data: [10, 20, 30], getSum: function() { return this.data.reduce((sum, num) => sum + num, 0); } }; console.log(stats.getSum()); // 60 console.log(stats.getSum.call(numbers)); // 15 (استخدام numbers.data)

طريقة apply()

طريقة apply() مشابهة لـ call()، لكنها تأخذ الوسائط كمصفوفة:

function introduce(greeting, punctuation) { console.log(`${greeting}، أنا ${this.name}${punctuation}`); } const person = { name: "أحمد" }; // استخدام call() - الوسائط تُمرر بشكل فردي introduce.call(person, "مرحباً", "!"); // استخدام apply() - الوسائط تُمرر كمصفوفة introduce.apply(person, ["مرحباً", "!"]); // مثال عملي: إيجاد أكبر رقم const numbers = [5, 6, 2, 3, 7, 1, 9, 4, 8]; // Math.max لا يقبل مصفوفة، لكن يمكننا استخدام apply const max = Math.max.apply(null, numbers); console.log(max); // 9 // بديل حديث مع عامل الانتشار const maxModern = Math.max(...numbers); console.log(maxModern); // 9

طريقة bind()

تنشئ طريقة bind() دالة جديدة بقيمة this ثابتة. على عكس call() و apply()، لا تستدعي الدالة فوراً:

const person = { name: "أحمد", greet: function() { console.log(`مرحباً، أنا ${this.name}`); } }; // مشكلة بدون bind setTimeout(person.greet, 1000); // مرحباً، أنا undefined // الحل: ربط 'this' بـ person setTimeout(person.greet.bind(person), 1000); // مرحباً، أنا أحمد // إنشاء دالة مربوطة const greetAhmad = person.greet.bind(person); greetAhmad(); // مرحباً، أنا أحمد (تعمل في أي مكان) // ربط مع وسائط function multiply(a, b) { return a * b; } const double = multiply.bind(null, 2); // تثبيت الوسيط الأول على 2 console.log(double(5)); // 10 (2 * 5) console.log(double(10)); // 20 (2 * 10) const triple = multiply.bind(null, 3); console.log(triple(5)); // 15 (3 * 5)
تذكر: bind() تُرجع دالة جديدة، بينما call() و apply() تستدعي الدالة فوراً.

'this' في معالجات الأحداث

في معالجات الأحداث، تشير this عادةً إلى العنصر الذي أطلق الحدث:

// HTML: <button id="myButton">انقر علي</button> const button = document.getElementById('myButton'); // دالة عادية: 'this' هو عنصر الزر button.addEventListener('click', function() { console.log(this); // <button id="myButton"> this.textContent = "تم النقر!"; this.style.backgroundColor = "green"; }); // دالة سهم: 'this' من النطاق الخارجي button.addEventListener('click', () => { console.log(this); // كائن Window (ليس الزر!) // this.textContent سيسبب خطأ }); // استخدام bind لتعيين 'this' مخصص const handler = { clickCount: 0, handleClick: function(event) { this.clickCount++; console.log(`تم النقر على الزر ${this.clickCount} مرات`); console.log(`العنصر:`, event.currentTarget); } }; button.addEventListener('click', handler.handleClick.bind(handler));

'this' في الفئات

في فئات ES6، تشير this إلى مثيل الفئة:

class Counter { constructor(initialValue = 0) { this.count = initialValue; } increment() { this.count++; console.log(`العدد: ${this.count}`); } decrement() { this.count--; console.log(`العدد: ${this.count}`); } // المشكلة: فقدان 'this' في المستدعيات startAutoIncrement() { setInterval(function() { this.increment(); // خطأ: this غير معرّفة }, 1000); } // الحل 1: دالة سهم startAutoIncrementArrow() { setInterval(() => { this.increment(); // يعمل! دالة السهم تحفظ 'this' }, 1000); } // الحل 2: bind startAutoIncrementBind() { setInterval(function() { this.increment(); }.bind(this), 1000); } } const counter = new Counter(10); counter.increment(); // العدد: 11 counter.startAutoIncrementArrow(); // يبدأ الزيادة التلقائية

دوال المُنشئ و 'this'

عند استخدام دوال المُنشئ، تشير this إلى الكائن المُنشأ حديثاً:

function Person(name, age) { // 'this' تشير إلى الكائن الجديد الذي يتم إنشاؤه this.name = name; this.age = age; this.greet = function() { console.log(`مرحباً، أنا ${this.name}`); }; } const ahmad = new Person("أحمد", 30); const sara = new Person("سارة", 25); ahmad.greet(); // مرحباً، أنا أحمد sara.greet(); // مرحباً، أنا سارة console.log(ahmad.name); // أحمد console.log(sara.age); // 25

مشاكل 'this' الشائعة والحلول

إليك المشاكل الأكثر شيوعاً مع this وحلولها:

// المشكلة 1: طريقة مستخرجة من الكائن const user = { name: "أحمد", sayName: function() { console.log(this.name); } }; const sayName = user.sayName; sayName(); // undefined // الحل: استخدام bind const boundSayName = user.sayName.bind(user); boundSayName(); // أحمد // المشكلة 2: دوال المستدعيات class Timer { constructor() { this.seconds = 0; } start() { // خطأ: يفقد 'this' // setInterval(this.tick, 1000); // الحل 1: دالة سهم setInterval(() => this.tick(), 1000); // الحل 2: Bind // setInterval(this.tick.bind(this), 1000); } tick() { this.seconds++; console.log(`${this.seconds} ثانية`); } } // المشكلة 3: دوال متداخلة const obj = { value: 42, method: function() { console.log(this.value); // 42 function innerFunction() { console.log(this.value); // undefined (this خاطئة) } innerFunction(); // الحل: دالة سهم const innerArrow = () => { console.log(this.value); // 42 (this صحيحة) }; innerArrow(); } };

تمرين تطبيقي:

التحدي: أنشئ فئة BankAccount بطرق تتعامل بشكل صحيح مع this. ضمّن طريقة تستخدم مستدعى (مثل setTimeout) دون فقدان سياق this.

الحل:

class BankAccount { constructor(owner, balance = 0) { this.owner = owner; this.balance = balance; this.transactions = []; } deposit(amount) { if (amount > 0) { this.balance += amount; this.transactions.push({ type: 'deposit', amount: amount, date: new Date() }); console.log(`تم الإيداع $${amount}. الرصيد الجديد: $${this.balance}`); } } withdraw(amount) { if (amount > 0 && amount <= this.balance) { this.balance -= amount; this.transactions.push({ type: 'withdraw', amount: amount, date: new Date() }); console.log(`تم السحب $${amount}. الرصيد الجديد: $${this.balance}`); } else { console.log("رصيد غير كافٍ"); } } // طريقة مع مستدعى - استخدام دالة سهم للحفاظ على 'this' scheduleDeposit(amount, delay) { console.log(`تم جدولة إيداع $${amount} في ${delay}ms`); setTimeout(() => { this.deposit(amount); // 'this' تشير بشكل صحيح إلى BankAccount }, delay); } getBalance() { return this.balance; } getStatement() { console.log(`مالك الحساب: ${this.owner}`); console.log(`الرصيد الحالي: $${this.balance}`); console.log(`المعاملات: ${this.transactions.length}`); } } const account = new BankAccount("أحمد علي", 1000); account.deposit(500); account.withdraw(200); account.scheduleDeposit(100, 2000); // يودع بعد ثانيتين account.getStatement();

الملخص

في هذا الدرس، تعلمت:

  • يتم تحديد this بواسطة كيفية استدعاء الدالة، وليس أين تم تعريفها
  • في النطاق العام، تشير this إلى الكائن العام (Window في المتصفحات)
  • في طرق الكائن، تشير this إلى الكائن
  • دوال السهم ترث this من نطاقها المعجمي
  • call() و apply() تستدعي دوالاً بـ this محدد
  • bind() تنشئ دالة جديدة بقيمة this ثابتة
  • معالجات الأحداث تعيّن this للعنصر الهدف
  • الفئات والمُنشئات تستخدم this للإشارة إلى المثيلات
  • المشاكل الشائعة والحلول للحفاظ على سياق this الصحيح
التالي: في الدرس التالي، سنغوص في الوعود - أساس JavaScript غير المتزامن الحديث!

ES
Edrees Salih
منذ 22 ساعة

We are still cooking the magic in the way!