إتقان JavaScript و TypeScript

الأساسيات الداخلية لمحرك JavaScript

5 دقيقة للقراءة

فهم كيفية تنفيذ JavaScript للكود هو أساس كل مقابلة واجهات أمامية. تظهر هذه المفاهيم في جولات البرمجة وأسئلة المعرفة وسيناريوهات التصحيح.

حلقة الأحداث (Event Loop)

JavaScript أحادي الخيط (single-threaded) لكنه غير محجوب (non-blocking) بفضل حلقة الأحداث. إليك نموذج التنفيذ:

┌─────────────────────────────────┐
│         مكدس الاستدعاءات         │  ← الكود المتزامن ينفذ هنا
│  (دالة واحدة في كل مرة)         │
└──────────┬──────────────────────┘
┌─────────────────────────────────┐
│       طابور المهام الصغيرة        │  ← Promise callbacks, queueMicrotask()
│  (يُفرغ بالكامل قبل              │     MutationObserver
│   الانتقال للمهام الكبيرة)        │
└──────────┬──────────────────────┘
┌─────────────────────────────────┐
│       طابور المهام الكبيرة        │  ← setTimeout, setInterval,
│  (مهمة واحدة لكل دورة)          │     I/O callbacks, UI rendering
└─────────────────────────────────┘

القاعدة الحاسمة: طابور المهام الصغيرة يُفرغ بالكامل قبل تشغيل المهمة الكبيرة التالية.

// سؤال مقابلة كلاسيكي: توقع ترتيب الإخراج
console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

// الإخراج: 1, 4, 3, 2
// لماذا: 1 و 4 متزامنان (مكدس الاستدعاءات).
// Promise.then مهمة صغيرة (تعمل قبل setTimeout).
// setTimeout مهمة كبيرة (تعمل أخيرًا).

الإغلاقات (Closures)

الإغلاق هو دالة تحتفظ بالوصول إلى نطاقها المعجمي حتى بعد عودة الدالة الخارجية.

// كلاسيكية المقابلات: مشكلة إغلاق الحلقة
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// الإخراج: 3, 3, 3 (var نطاقها الدالة وليس الكتلة)

// الحل 1: استخدام let (نطاق الكتلة)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// الإخراج: 0, 1, 2

// الحل 2: IIFE إغلاق
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => console.log(j), 100);
  })(i);
}
// الإخراج: 0, 1, 2

نمط إغلاق عملي — التخزين المؤقت (Memoization):

function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const expensiveCalc = memoize((n) => {
  console.log('Computing...');
  return n * n;
});

expensiveCalc(5); // Computing... 25
expensiveCalc(5); // 25 (مخزّن، بدون log)

الكلمة المفتاحية this

لدى JavaScript أربع قواعد لربط this، تُطبق بترتيب الأولوية هذا:

القاعدة متى this هو
1. ربط new new Foo() الكائن المنشأ حديثًا
2. الربط الصريح call(), apply(), bind() الكائن المحدد
3. الربط الضمني obj.method() الكائن قبل النقطة
4. الربط الافتراضي استدعاء دالة عادي undefined (صارم) أو globalThis
const obj = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  },
  greetLater() {
    // الدوال السهمية ترث `this` من النطاق المحيط
    setTimeout(() => console.log(this.name), 100);
  }
};

obj.greet();      // 'Alice' (ربط ضمني)
obj.greetLater(); // 'Alice' (الدالة السهمية ترث `this`)

const fn = obj.greet;
fn();             // undefined (ربط افتراضي، فقدان السياق)

نصيحة للمقابلات: الدوال السهمية ليس لديها this خاص بها. ترث this من النطاق المعجمي المحيط. لهذا تُفضل الدوال السهمية في callbacks ومعالجات الأحداث داخل methods الفئات أو مكونات React.

الوراثة النموذجية (Prototypal Inheritance)

كل كائن JavaScript له رابط [[Prototype]] داخلي. عمليات البحث عن الخصائص تتبع سلسلة النموذج الأولي:

const animal = {
  speak() {
    return `${this.name} makes a sound`;
  }
};

const dog = Object.create(animal);
dog.name = 'Rex';
dog.speak(); // 'Rex makes a sound' (وُجد عبر سلسلة النموذج الأولي)

// صيغة فئات ES6 هي سكر نحوي فوق النماذج الأولية
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  bark() {
    return `${this.name} barks`;
  }
}

WeakMap و WeakSet

هذه أقل شيوعًا لكنها تظهر في مقابلات المستوى الأعلى:

// WeakMap: المفاتيح يجب أن تكون كائنات، ويتم جمعها
// تلقائيًا عندما لا توجد مراجع أخرى
const metadata = new WeakMap();

function process(obj) {
  if (!metadata.has(obj)) {
    metadata.set(obj, { processedAt: Date.now() });
  }
  return metadata.get(obj);
}

// حالة الاستخدام: إرفاق بيانات بعناصر DOM بدون تسرب ذاكرة
// عند إزالة العنصر من DOM، يتم جمع
// إدخال WeakMap تلقائيًا
الميزة Map WeakMap
أنواع المفاتيح أي نوع كائنات فقط
قابل للتعداد نعم (forEach, keys()) لا
جمع القمامة يمنع GC للمفاتيح يسمح بـ GC للمفاتيح
حالة الاستخدام استخدام عام البيانات الوصفية، التخزين المؤقت بدون تسرب

التالي: سنغطي أنماط TypeScript التي تظهر بشكل متكرر في مقابلات الواجهات الأمامية. :::

اختبار

الوحدة 2: إتقان JavaScript و TypeScript

خذ الاختبار