إتقان JavaScript و TypeScript

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

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

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

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

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

دورة واحدة من حلقة الأحداث

سؤال الترتيب في كل فحص هاتفي ينبع من هذه الدورة. لاحظ أين يقع العرض: ليس مهمة في طابور تنافس دوالك، بل مرحلة يبلغها المتصفح مرة في الدورة — ولهذا تحجب المهمة الطويلة الرسم كما تحجب الإدخال.

يفرغ المكدسإن كان سيَعرض هذه الدورةانتهت الدوالالدورة التاليةدُفعت الدالةمكدس الاستدعاءاتالكود المتزامن، إطار واحد في كل…واحدة لكل دورةمهمة كبيرة واحدةsetTimeout أو setInterval أو دا…تفريغ كاملنقطة تفتيش المهام الصغيرةدوال Promise وqueueMicrotask وM…requestAnimationFrameلا يعمل إلا إذا قرر المتصفح الع…قد تُتخطىالنمط والتخطيط والرسمفرصة عرض، لا مدخل في طابور. قد …

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

لذلك فإن سلسلة لا نهائية من المهام الصغيرة تُجوّع الصفحة إلى الأبد: كل واحدة تضيف التالية، ولا ينتهي التفريغ، ولا يبلغ المتصفح مرحلة العرض أبدًا. أما سلسلة لا نهائية من setTimeout(fn, 0) فلا تفعل ذلك، لأن كل دالة مهمة كبيرة مستقلة وبينها نقطة تفتيش. هذا التمييز هو سؤال المتابعة حين يجيب المرشح على سؤال الترتيب إجابة صحيحة.

// سؤال مقابلة كلاسيكي: توقع ترتيب الإخراج
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. ربط newnew 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 تلقائيًا
الميزةMapWeakMap
أنواع المفاتيحأي نوعكائنات فقط
قابل للتعدادنعم (forEach, keys())لا
جمع القمامةيمنع GC للمفاتيحيسمح بـ GC للمفاتيح
حالة الاستخداماستخدام عامالبيانات الوصفية، التخزين المؤقت بدون تسرب

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

اختبار

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

خذ الاختبار
هل كان هذا الدرس مفيدًا؟

سجّل الدخول للتقييم