إتقان JavaScript و TypeScript
الأساسيات الداخلية لمحرك JavaScript
فهم كيفية تنفيذ 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 التي تظهر بشكل متكرر في مقابلات الواجهات الأمامية. :::