دليل سريع للاستعداد لمقابلات JavaScript: أهم المفاهيم التي تمنحك الأفضلية
مقدمة
إذا كنت تستعد لخوض مقابلة تقنية لوظيفة تتطلب إتقان JavaScript، فأنت بحاجة إلى مراجعة ذكية تركز على المفاهيم الأكثر تكراراً في الأسئلة، لا إلى قراءة مطولة تستهلك وقتك دون عائد عملي. هذا الدليل صُمم ليكون مرجعاً مركزاً يساعدك على تثبيت الأساسيات، وفهم المفاهيم المتوسطة والمتقدمة، واستيعاب الأفكار التي تظهر كثيراً في مقابلات المطورين.
ستجد هنا شرحاً احترافياً لأهم محاور JavaScript، بداية من المتغيرات والمصفوفات، مروراً بالنطاق Scope وميزة Closures، وصولاً إلى البرمجة غير المتزامنة Asynchronous JavaScript، والوعود Promises، وموضوعات متقدمة مثل debouncing وthrottling وasync وdefer.

المتطلبات الأساسية قبل البدء
- فهم أساسي لكيفية عمل الويب.
- معرفة أولية بـ
HTMLوCSS. - إلمام جيد بأساسيات
JavaScript، خصوصاً صياغةES6+.
فهرس الموضوعات
- أساسيات
JavaScriptوالمتغيرات وطرق المقارنة. - المصفوفات وأشهر دوالها.
- الدوال والبرمجة الوظيفية.
- النطاق
ScopeوClosuresوHoisting. - الكائنات
Objectsوالكلمة المفتاحيةthis. Prototypesوالوراثة النموذجية.- البرمجة غير المتزامنة وحلقة الأحداث
Event Loop. - المؤقتات
setTimeout()وsetInterval(). - الوعود
Promisesوasync/await. - مفاهيم متقدمة:
Polyfillsوasync/deferوdebouncingوthrottling. - التخزين عبر
localStorageوsessionStorage.
أساسيات JavaScript
المتغيرات في JavaScript
المتغيرات هي اللبنات الأساسية في أي لغة برمجة. نستخدمها لتخزين القيم، سواء كانت أرقاماً أو نصوصاً أو كائنات أو غير ذلك. وتمتاز JavaScript بأنها لغة ذات أنواع مرنة loosely-typed، أي أنك لا تحتاج إلى التصريح بنوع المتغير يدوياً في معظم الحالات.
في JavaScript توجد ثلاث طرق شائعة لتعريف المتغيرات: var وlet وconst.

الفروقات الأساسية بينها تتعلق بإعادة التعريف، وإمكانية التعديل، ونطاق الوصول scope.
var a = 3
var a = 4
console.log(a) // 4
let b = 3
// let b = 4 // Syntax Error
b = 4
const c = 3
// const c = 4 // Syntax Error
const d // Syntax Error
يمكن إعادة تعريف المتغير المعرّف بواسطة var، بينما لا يمكن ذلك مع let وconst. كما أن const يتطلب تهيئة فورية عند التعريف.
الفرق بين == و===
هذا السؤال من أكثر الأسئلة شيوعاً في المقابلات. العامل == يقارن القيم بعد محاولة تحويل الأنواع تلقائياً، بينما العامل === يقارن القيمة والنوع معاً دون تحويل.
let a = 5
let b = '5'
console.log(a == b) // true
console.log(a === b) // false
في المقابلات العملية، يُنصح غالباً باستخدام === لتجنب السلوكيات غير المتوقعة الناتجة عن التحويل الضمني للأنواع.
المصفوفات في JavaScript
عندما يزداد عدد القيم التي تتعامل معها، يصبح من الأفضل جمعها في بنية منظمة مثل Array. المصفوفة تتيح تخزين مجموعة من العناصر والوصول إليها وتعديل ترتيبها ومعالجتها باستخدام دوال جاهزة قوية.
let a = 4
const b = 5
var c = 'hello'
const array = [a, b, c]
const arr = [4, 5, 'hello']
أشهر دوال المصفوفات
من أكثر الدوال استخداماً في المقابلات: map() وfilter() وfind() وreduce() وforEach().
استخدام map()
الدالة map() تنشئ مصفوفة جديدة اعتماداً على المصفوفة الأصلية، دون تعديلها مباشرة. وهي مثالية عندما تريد تحويل كل عنصر إلى شكل جديد.
const a = [1, 2, 3, 4, 5]
const d = a.map(function(item) {
return item * 2
})
console.log(d) // [2, 4, 6, 8, 10]
استخدام filter()
الدالة filter() تعيد مصفوفة جديدة تحتوي فقط على العناصر التي تحقق شرطاً معيناً.
const words = ['react', 'script', 'interview', 'style', 'javascript']
const ans = words.filter((word) => word.length > 6)
console.log(ans) // ['interview', 'javascript']
ويمكنك تنفيذ الفكرة نفسها بدون الاعتماد على الدوال الجاهزة إذا طُلب منك ذلك في المقابلة:
let newArr = []
for (let i = 0; i < words.length; i++) {
if (words[i].length > 6) {
newArr.push(words[i])
}
}
console.log(newArr)
الفرق بين forEach() وmap()
رغم التشابه الظاهري بين forEach() وmap()، فإن بينهما فرقين مهمين:
map()تعيد مصفوفة جديدة.forEach()لا تعيد قيمة مفيدة عادة.
let arr = [1, 2, 3, 4, 5, 6, 7]
function consoleEven(arr) {
let data = arr.map((num) => (num % 2 === 0 ? num * 2 : num * 1))
console.log(data)
}
consoleEven(arr)
function consoleEven(arr) {
let data = arr.forEach((num) => (num % 2 === 0 ? num * 2 : num * 1))
console.log(data) // undefined
}
consoleEven(arr)
كما أن map() تسمح بما يسمى method chaining، وهو أمر لا يتحقق بالطريقة نفسها مع forEach().
function consoleEven(arr) {
let data = arr
.map((num) => (num % 2 === 0 ? num * 2 : num))
.map((item) => (item % 2 === 0 ? item / 2 : item))
console.log(data)
}
consoleEven(arr)
ومن النقاط المهمة في المقابلات: كل من map() وforEach() لا يغيران المصفوفة الأصلية بشكل مباشر في هذا السياق.
البرمجة الوظيفية في JavaScript
تعريف الدوال
يمكن تخزين جزء من المنطق البرمجي داخل دالة لإعادة استخدامه. في JavaScript يمكنك كتابة الدوال بطريقتين شائعتين: الدوال التقليدية، ودوال الأسهم Arrow Functions.
function a() {
console.log('I am a normal function')
}
const b = () => {
console.log('I am an arrow function')
}
const c = (name) => {
console.log(`My name is ${name}`)
}
كما يمكن تمرير دالة إلى دالة أخرى، وهو مبدأ أساسي في البرمجة الوظيفية.
const greet = () => {
const prefix = 'Mr'
return (name) => {
console.log(`${prefix} ${name}, welcome!`)
}
}
greet()('Jack')
النطاق Scope في JavaScript
يشير Scope إلى المكان الذي يمكن من خلاله الوصول إلى المتغيرات. وهناك ثلاثة أنواع رئيسية:
- النطاق العام
Global Scope. - نطاق الدالة
Function Scope. - نطاق الكتلة
Block Scope.
المتغيرات المعرفة بـ var تختلف عن let وconst من حيث النطاق، وهذه من النقاط الأساسية في أي مقابلة.
var a = 5
function adder() {
let b = 7
console.log(a + b)
}
console.log(adder())
// console.log(b) // Error
{
const c = 10
console.log(c)
}
// console.log(c) // Error
فهم Closures في JavaScript
تُعد Closures من أهم المفاهيم التي تُسأل عنها كثيراً. ببساطة، يحدث closure عندما تحتفظ دالة داخلية بإمكانية الوصول إلى متغيرات الدالة الخارجية، حتى بعد انتهاء تنفيذ الدالة الخارجية.
const greet = () => {
const prefix = 'Mr'
return (name) => {
console.log(`${prefix} ${name}, welcome!`)
}
}
greet()('Jack')
في المثال السابق، المتغير prefix بقي متاحاً للدالة الداخلية، وهذا هو جوهر closure.
مثال أوضح:
function x() {
var a = 7
function y() {
console.log(a)
}
return y
}
var z = x()
console.log(z)
z()
عند استدعاء z()، ستتمكن الدالة y() من الوصول إلى المتغير a لأن الدالة احتفظت بسياقها المعجمي lexical environment.
فوائد Closures
- تنفيذ مفهوم
Currying. - إخفاء البيانات
Data Hiding. - تغليف السلوك داخل واجهات محددة.
let add = function(x) {
return function(y) {
console.log(x + y)
}
}
let addByTwo = add(2)
addByTwo(3)
function Counter() {
var count = 0
this.incrementCount = function() {
count++
console.log(count)
}
}
var adder = new Counter()
adder.incrementCount() // 1
عيوب Closures
رغم أهميتها، قد تسبب closures استهلاكاً زائداً للذاكرة إذا أسيء استخدامها، لأن بعض المتغيرات تظل محتفظة بمرجع داخل الذاكرة لفترة أطول من اللازم.
مفهوم Hoisting
Hoisting هو سلوك افتراضي في JavaScript يتم فيه رفع التصريحات إلى أعلى النطاق أثناء مرحلة التحضير للتنفيذ.
- المتغيرات المعرّفة بـ
varتُرفع وتُهيأ بالقيمةundefined. - المتغيرات المعرّفة بـ
letوconstتُرفع ولكن لا تُهيأ مباشرة. - تعريفات الدوال تُرفع أيضاً.
function consoleNum() {
console.log(num)
var num = 10
}
consoleNum() // undefined
يرى محرك التنفيذ هذا الكود أقرب إلى الشكل التالي:
function consoleNum() {
var num
console.log(num)
num = 10
}
الكائنات في JavaScript
الكائنات Objects تُستخدم لتخزين البيانات على هيئة أزواج key-value. وهي من أكثر البنى استخداماً في التطبيقات الواقعية.
const developer = {
name: 'Raj',
age: 22
}
في هذا المثال، name هو المفتاح، وRaj هي القيمة.
ما معنى this في JavaScript؟
الكلمة المفتاحية this من أكثر المواضيع التي تربك المبتدئين وتظهر كثيراً في المقابلات. الفكرة الأساسية أن قيمة this تعتمد على طريقة استدعاء الدالة ومكانها.
console.log(this)
في بيئة المتصفح، ستشير غالباً إلى كائن window عند الاستدعاء العام.
function myFunc() {
console.log(this)
}
const obj = {
bool: true,
myFunc: myFunc,
}
obj.myFunc()
في هذا السياق، ستشير this إلى الكائن obj لأن الاستدعاء تم من خلاله.
myFunc() // window
هذا ما يُعرف باسم Implicit Binding.
الربط الصريح Explicit Binding مع call()
عندما تريد إجبار دالة على استخدام كائن معين كسياق لها، يمكنك استخدام call().
const student_1 = {
name: 'Randall',
displayName_1: function displayName() {
console.log(this.name)
}
}
const student_2 = {
name: 'Raj',
displayName_2: function displayName() {
console.log(this.name)
}
}
student_1.displayName_1()
student_2.displayName_2()
ولتقليل التكرار:
student_1.displayName_1.call(student_2) // Raj

const myData = {
name: 'Rajat',
city: 'Delhi',
displayStay: function() {
console.log(this.name, 'stays in', this.city)
},
}
myData.displayStay()
const yourData = {
name: 'name',
city: 'city'
}
myData.displayStay.call(yourData)
انتبه أيضاً إلى أن دوال الأسهم Arrow Functions لا تنشئ قيمة خاصة بها لـ this، بل ترثها من النطاق الخارجي.
النماذج الأولية والوراثة النموذجية
ما هي Prototypes؟
عند إنشاء كائن أو دالة أو مصفوفة في JavaScript، يضيف المحرك خصائص وطرقاً داخلية مرتبطة بما يسمى prototype. لهذا السبب تجد أن المصفوفات تمتلك دوالاً مثل forEach() وmap() دون تعريفها يدوياً.
let arr = ['Rajat', 'Raj']
console.log(arr.__proto__.forEach)
console.log(arr.__proto__)
console.log(arr.__proto__.__proto__)
console.log(arr.__proto__.__proto__.__proto__)
تُعرف هذه السلسلة باسم prototype chain.
الوراثة النموذجية Prototypal Inheritance
let object = {
name: 'Rajat',
city: 'Delhi',
getIntro: function() {
console.log(`${this.name}, ${this.city}`)
},
}
let object2 = {
name: 'Aditya',
}
object2.__proto__ = object
console.log(object2.city)
بعد ربط object2 بـ object عبر النموذج الأولي، سيتمكن من الوصول إلى الخصائص غير الموجودة داخله مباشرة. هذا هو جوهر الوراثة النموذجية.
البرمجة غير المتزامنة في JavaScript
JavaScript لغة أحادية الخيط single-threaded، أي أنها تنفذ مهمة واحدة في كل لحظة. لكن تطبيقات الويب تحتاج غالباً إلى التعامل مع عمليات تستغرق وقتاً غير معروف، مثل جلب البيانات من الخادم. هنا تظهر أهمية البرمجة غير المتزامنة.
حلقة الأحداث Event Loop
لفهم السلوك غير المتزامن جيداً، من الضروري استيعاب دور Event Loop في تنسيق تنفيذ المهام بين مكدس الاستدعاءات Call Stack وطوابير المهام.
في المقابلات، لا يكفي حفظ التعريف؛ الأهم أن تفهم لماذا لا تُنفَّذ بعض الأوامر فوراً رغم ظهورها مبكراً في الكود.
المؤقتات: setTimeout() وsetInterval() وclearInterval()
setTimeout(() => {
console.log('Here - I am after 2 seconds')
}, 2000)
const timer = setInterval(() => {
console.log('I will keep on coming back until you clear me')
}, 2000)
clearInterval(timer)
مثال شائع في المقابلات:
console.log('Hello')
setTimeout(() => {
console.log('lovely')
}, 0)
console.log('reader')
// Hello
// reader
// lovely
رغم أن التأخير في setTimeout() يساوي 0، فإن التنفيذ لا يحدث مباشرة، لأن الدالة تُرسل إلى قائمة الانتظار وتنتظر فراغ مكدس التنفيذ.
مثال أصعب قليلاً:
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}
// 6 6 6 6 6
السبب أن المتغير i معرّف بواسطة var، وبحلول وقت تنفيذ الدوال المؤجلة تكون الحلقة قد انتهت وأصبحت قيمة i تساوي 6.
والحل الأبسط:
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}
الوعود Promises في JavaScript
تُعد Promises من أهم أسئلة المقابلات الحديثة. وهي تمثل نتيجة مستقبلية لعملية غير متزامنة، قد تنجح أو تفشل.
حالات Promise الأساسية:
Pending: حالة الانتظار.Fulfilled: تم التنفيذ بنجاح.Rejected: حدث فشل.
const promise = new Promise((resolve, reject) => {
let value = true
if (value) {
resolve('hey value is true')
} else {
reject('there was an error, value is false')
}
})
promise
.then((x) => {
console.log(x)
})
.catch((err) => console.log(err))
ولا يشترط أن تكون أسماء المعاملات resolve وreject ثابتة، لكنها التسمية الاصطلاحية المتعارف عليها.
استخدام async/await
توفر async/await صياغة أوضح وأسهل للقراءة مقارنة بسلاسل then() وcatch().
async function asyncCall() {
const result = await promise
console.log(result)
}
asyncCall()
من مزايا هذا الأسلوب أنه يجعل تدفق الكود أقرب إلى الكود المتزامن، ويقلل من التعقيد مقارنة بما كان يعرف سابقاً بـ callback hell.
مفاهيم JavaScript متقدمة مهمة في المقابلات
ما هي Polyfills؟
Polyfill هو كود يُستخدم لتوفير ميزة حديثة في متصفحات قديمة لا تدعمها بشكل أصلي.
مثال على بناء نسخة مبسطة من map():
Array.prototype.myMap = function(cb) {
var arr = []
for (var i = 0; i < this.length; i++) {
arr.push(cb(this[i], i, this))
}
return arr
}
const arr = [1, 2, 3]
console.log(arr.myMap((a) => a * 2)) // [2, 4, 6]
الفرق بين async وdefer في وسم script
عند تحميل ملفات JavaScript الخارجية في صفحة الويب، يمكن استخدام الخاصيتين async وdefer لتحسين التحميل والأداء.




- استخدم
deferعندما تكون الملفات البرمجية معتمدة على بعضها ويجب تنفيذها بالترتيب. - استخدم
asyncعندما يكون الملف مستقلاً ولا يعتمد على ترتيب معين.
نقطة مهمة في المقابلات: الخاصية async لا تضمن ترتيب التنفيذ.
مفهوم Debouncing
Debouncing أسلوب يمنع تكرار استدعاء الدالة بشكل مفرط، ويؤجل التنفيذ حتى يتوقف الحدث لفترة زمنية محددة. هذا شائع جداً في حقول البحث.
<input type='text' id='text' />
const getData = (e) => {
console.log(e.target.value)
}
const inputField = document.getElementById('text')
const debounce = function(fn, delay) {
let timer
return function() {
let context = this
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, arguments)
}, delay)
}
}
inputField.addEventListener('keyup', debounce(getData, 300))
الفكرة هنا أن الدالة getData لن تعمل عند كل ضغطة فوراً، بل بعد مرور 300ms دون حدث جديد.
تحدٍ بسيط مشابه باستخدام العد التنازلي:
let count = 10
for (let i = 0; i < 10; i++) {
function timer(i) {
setTimeout(() => {
console.log(count)
count--
}, i * 500)
}
timer(i)
}
مفهوم Throttling
Throttling يضمن تنفيذ الدالة مرة واحدة فقط خلال فترة زمنية محددة، مهما تكرر الحدث داخل هذه الفترة. وهو مناسب للأحداث المتكررة جداً مثل تغيير حجم النافذة أو التمرير.
const expensive = () => {
console.log('expensive')
}
const throttle = (fn, limit) => {
let context = this
let flag = true
return function() {
if (flag) {
fn.apply(context, arguments)
flag = false
}
setTimeout(() => {
flag = true
}, limit)
}
}
const func = throttle(expensive, 2000)
window.addEventListener('resize', func)
الفرق بين Debouncing وThrottling
| المفهوم | السلوك | الاستخدام الشائع |
|---|---|---|
Debouncing |
ينفذ بعد توقف الحدث لفترة محددة | حقول البحث والإدخال |
Throttling |
ينفذ مرة كل فترة زمنية ثابتة | التمرير وتغيير حجم النافذة |
إذا كنت تريد تقليل عدد الطلبات أثناء الكتابة في مربع البحث، فاستخدم debouncing. وإذا كنت تريد منع تنفيذ دالة ثقيلة عشرات المرات أثناء التمرير، فاستخدم throttling.
التخزين في JavaScript
من الموضوعات الصغيرة ولكن المهمة في المقابلات: آليات التخزين داخل المتصفح.
localStorage: يحتفظ بالبيانات حتى بعد إغلاق المتصفح.sessionStorage: يحتفظ بالبيانات فقط حتى انتهاء الجلسة أو إغلاق التبويب.
// save
localStorage.setItem('key', 'value')
// get
let data = localStorage.getItem('key')
// remove
localStorage.removeItem('key')
والأوامر نفسها تقريباً تنطبق على sessionStorage.
نصائح عملية لاجتياز مقابلة JavaScript بنجاح
- لا تكتفِ بحفظ التعريفات؛ افهم سبب السلوك البرمجي.
- تدرّب على كتابة الأمثلة بنفسك دون نسخ مباشر.
- راجع الأسئلة الخاصة بـ
thisوclosuresوpromisesأكثر من مرة. - كن قادراً على شرح الفروقات بين
varوletوconstبوضوح. - تعلم متى تستخدم
map()ومتى تستخدمforEach()أوfilter(). - توقع أسئلة تطبيقية، لا نظرية فقط.
الخلاصة التقنية
إذا أردنا تلخيص أهم ما يميز المرشح القوي في مقابلات JavaScript، فسنجد أن التفوق لا يعتمد على كثرة المصطلحات بقدر ما يعتمد على الفهم العميق لسلوك اللغة. المفاهيم مثل scope وclosure وthis وevent loop وpromises تمثل العمود الفقري لأي تقييم تقني حقيقي. وكلما تمكنت من ربط هذه المفاهيم بأمثلة عملية واضحة، زادت قدرتك على الإجابة بثقة وإقناع. هذا الدليل ليس بديلاً عن الممارسة، لكنه خريطة مركزة لأكثر ما تحتاجه فعلاً قبل المقابلة.