محمد قتيبة شيخانيأبني. أستكشف. أشارك

11من 22

تطوير الويب

لغة JavaScript: بثّ الحياة في صفحتك

لقطة شاشة لمحرّر VS Code فيه مجلّد my-first-site وملفّ script.js مفتوح: متغيّر themeButton، ودالّة applyTheme تبدّل الصنف dark-mode وتضبط aria-pressed، وقراءة الاختيار المحفوظ من localStorage داخل try وcatch، ثمّ matchMedia للوضع الداكن. وتحت اللقطة شعار JS الأصفر وعنوان المقالة «لغة JavaScript».

في 30 أيلول 2026 كانت 98.9% من مواقع الويب تشغّل JavaScript في متصفّح زائرها، بحسب إحصاءات W3Techs.[1] وفي استطلاع Stack Overflow لمطوّري 2025، وهو آخر استطلاعٍ منشور حتّى الآن، كانت أكثر لغة برمجةٍ استعمالًا بين المشاركين، بنسبة 66%،[2] فإن تعلّمت لغةً واحدة بعد HTML وCSS، فهذه هي.

وصفحة المشروع التي كسوتها في مقال CSS وجعلتها تتكيّف مع كلّ شاشة في مقال التصميم المتجاوب لا تفعل شيئًا إن نقرت فيها. وزرّ القائمة الذي كتبته هناك ما زال ينتظر من يبدّل قيمته. في هذا المقال تتعرّف إلى اللغة من أوّلها، ثمّ تكتب ذلك السطر، ثمّ تضيف إلى صفحتك زرًّا يبدّلها بين الوضعين الفاتح والداكن ويتذكّر اختيار الزائر.

ما هي JavaScript؟

تعرّفها موسوعة MDN بأنّها لغة برمجةٍ تتيح لك أن تضيف إلى صفحات الويب ميزاتٍ معقّدة، في كلّ مرّةٍ تفعل فيها الصفحة أكثر من أن تبقى ثابتة.[3] وأوضح ما يفرّق بين اللغات الثلاث مثالٌ واحد. تقول HTML: هنا زرّ. وتقول CSS: اجعله بإطارٍ أحمر وزوايا مستديرة. وتقول JavaScript: حين ينقر الزائر هذا الزرّ، غيّر ألوان الصفحة واحفظ اختياره.

والفرق بينها أعمق من الوظيفة، فالأوليان تصفان والثالثة تنفّذ: فيها متغيّراتٌ تحفظ القيم، وشروطٌ تقرّر، وحلقاتٌ تكرّر، ودوالّ تُستدعى، ولذلك يقرؤها في المتصفّح برنامجٌ خاصّ اسمه محرّك JavaScript. ففي Chrome محرّك V8، وفي Firefox محرّك SpiderMonkey من Mozilla، وفي Safari محرّك JavaScriptCore المدمج في WebKit،[4] والمحرّكات مختلفة لكنّها تنفّذ اللغة نفسها، فتعمل شيفرتك في المتصفّحات الثلاثة.

وكانت JavaScript سنواتٍ لغة المتصفّح وحده، حتّى بدأ المبرمج رايان دال في مطلع 2009 يطوّر Node.js، وهو بيئةٌ تشغّل اللغة على الخادم.[5] ويقول موقع V8 إنّ محرّكه يعمل في Chrome وفي Node.js معًا، فصار بإمكان المطوّر أن يكتب الواجهة والخادم بلغةٍ واحدة، ويقرأ بها الملفّات ويتّصل بقواعد البيانات. بل إنّ المحرّر الذي تكتب فيه، VS Code، يعمل على غلافٍ اسمه Electron، مبنيٍّ بتقنيات الويب نفسها.[6]

شكل 1 لغةٌ واحدة ومضيفان عمودان. في اليمين المتصفّح: ملفّ script.js، تحته محرّك JavaScript المميّز، وهو V8 أو SpiderMonkey أو JavaScriptCore بحسب المتصفّح، وتحته ما يعطيه المتصفّح للشيفرة: الصفحة والأحداث والتخزين. وفي اليسار Node.js: ملفّ server.js، تحته المحرّك المميّز V8، وتحته الملفّات والشبكة وقواعد البيانات. المتصفّح script.js محرّك JavaScript V8 · SpiderMonkey · JavaScriptCore الصفحة والأحداث والتخزين Node.js server.js محرّك JavaScript V8 الملفّات والشبكة وقواعد البيانات لغةٌ واحدة ومضيفان في الأعلى المتصفّح: ملفّ script.js، ثمّ محرّك JavaScript المميّز V8 أو SpiderMonkey أو JavaScriptCore، ثمّ الصفحة والأحداث والتخزين. وفي الأسفل Node.js: ملفّ server.js، ثمّ المحرّك المميّز V8، ثمّ الملفّات والشبكة وقواعد البيانات. المتصفّح script.js محرّك JavaScript V8 · SpiderMonkey · JavaScriptCore الصفحة والأحداث والتخزين Node.js server.js محرّك JavaScript V8 الملفّات والشبكة وقواعد البيانات
لغةٌ واحدة ومضيفان: في المتصفّح يعطي المحرّكُ الشيفرةَ الصفحة والأحداث والتخزين، وفي Node.js يعطيها الملفّات والشبكة وقواعد البيانات. والمحرّك في المنتصف هو ما يجمعهما.

من أين جاءت JavaScript

أوثق روايةٍ للقصّة كتبها صاحبها نفسه. ففي بحثٍ نُشر سنة 2020 مع ألن ويرفس-بروك، يروي بريندان آيك أنّه صمّم اللغة ونفّذها في شركة Netscape في أيّار 1995، في نموذجٍ أوّليّ استغرق عشرة أيّام.[5] وكان اسمها في الشركة Mocha، ثمّ خرجت إلى الناس في أيلول 1995 باسم LiveScript داخل النسخة التجريبيّة من متصفّح Netscape Navigator 2.0. وفي 4 كانون الأوّل 1995 أعلنت Netscape وشركة Sun معًا اسمها الأخير: JavaScript، وهذه صورة صاحبها بعد ذلك بسبعة عشر عامًا.[7]

ولا يغرّك الاسم، فاللغتان Java وJavaScript مختلفتان في تصميمهما واستعمالهما، والشبه بينهما في الاسم وبعض الكتابة فقط. أمّا ما بعد الإطلاق فيرويه البحث نفسه: كتبت مايكروسوفت نسختها الخاصّة من اللغة، وسمّتها JScript، بالهندسة العكسيّة لما رأته في متصفّح Netscape. فوجد المطوّرون في السنة الأولى أنّ الشيفرة نفسها لا تعمل بالطريقة نفسها في المتصفّحين.

صورةٌ شخصيّة لبريندان آيك مبتسمًا، بنظّارةٍ وسترةٍ داكنة وقميصٍ أبيض مفتوح الياقة، أمام خلفيّةٍ رماديّة.
بريندان آيك، الذي صمّم JavaScript في Netscape سنة 1995، في صورته الرسميّة لمؤسّسة Mozilla سنة 2012.صورة دارسي باديا (Darcy Padilla) سنة 2012، عبر ويكيميديا كومنز، برخصة CC BY-SA 3.0

ولهذا بدأ توحيد اللغة سنة 1996 في منظّمة المعايير Ecma International، وصدرت الطبعة الأولى من المعيار في حزيران 1997 باسم ECMAScript.[5] فالمعيار هو الوصفة، وJavaScript هي اللغة التي تطبخها المتصفّحات منها. وأكبر قفزةٍ في تاريخه الطبعة السادسة في حزيران 2015، ويسمّيها المطوّرون ES2015 أو ES6، وجاءت بـlet وconst والدوالّ السهميّة وغيرها ممّا ستكتبه في هذا المقال. ومنذ ذلك الحين تصدر طبعةٌ كلّ حزيران، وآخرها ECMAScript 2026، الطبعة السابعة عشرة، في حزيران 2026.[8]

أين تضع السكربت في صفحتك

تكتب الشيفرة في ملفٍّ مستقلّ بامتداد ‎.js، كما فعلت مع CSS، وتربطه بالصفحة بوسم script، وستقرأ في شروحٍ كثيرة، ومنها النسخة السابقة من هذا المقال، أن تضعه قبل </body> مباشرة. كانت هذه نصيحةً صحيحة يومًا، ولها سبب: المتصفّح يقرأ الصفحة من أعلاها، فإن بلغ سكربتًا عاديًّا في head أوقف قراءتها حتّى ينزّله وينفّذه. وتقول MDN إنّ السكربت الذي لا يحمل defer ولا async يوقف تحليل الصفحة.[9] فإن بحث عن زرٍّ لم يُقرأ بعد، لم يجده.

والحلّ الأنظف اليوم كلمةٌ واحدة: defer. ضع الوسم في head كما تضع ملفّ CSS، وأضف إليه هذه السمة:

<script src="script.js" defer></script>

فيبدأ المتصفّح تنزيل الملفّ مبكّرًا وهو يكمل قراءة الصفحة، ثمّ ينفّذه بعد أن ينتهي تحليل المستند كلّه، وقبل الحدث DOMContentLoaded. فتجد شيفرتك كلّ عنصرٍ في مكانه، ولا تنتظر الصفحة تنزيل الملفّ. وهذا يتّفق مع ما قرأته في مقال المتصفّح عن defer وasync.

شكل 2 متى يعمل السكربت خطّان زمنيّان يجري فيهما الزمن من اليمين إلى اليسار. في الأعلى وسم script عاديّ في head: تحليل HTML، ثمّ يتوقّف التحليل لتنزيل الملفّ ثمّ تنفيذه، ثمّ يكمل التحليل. وفي الأسفل المميّز مع defer: تحليل HTML مستمرٌّ بلا توقّف، والتنزيل يجري تحته في الوقت نفسه، والتنفيذ بعد انتهاء التحليل، فينتهي كلّ شيءٍ أبكر. <script> تحليل HTML تنزيل تنفيذ تحليل HTML <script defer> تحليل HTML تنفيذ تنزيل الزمن متى يعمل السكربت خطّان زمنيّان يجري فيهما الزمن من اليمين إلى اليسار. في الأعلى وسم script عاديّ في head: تحليل HTML، ثمّ يتوقّف التحليل لتنزيل الملفّ ثمّ تنفيذه، ثمّ يكمل التحليل. وفي الأسفل المميّز مع defer: تحليل HTML مستمرٌّ بلا توقّف، والتنزيل يجري تحته في الوقت نفسه، والتنفيذ بعد انتهاء التحليل، فينتهي كلّ شيءٍ أبكر. <script> تحليل HTML تنزيل تنفيذ تحليل HTML <script defer> تحليل HTML تنفيذ تنزيل الزمن
الزمن يجري من اليمين إلى اليسار. السكربت العاديّ في head يوقف تحليل الصفحة حتّى يُنزَّل ويُنفَّذ، ومع defer يجري التنزيل مع التحليل، ثمّ يُنفَّذ السكربت بعد انتهائه، فينتهي كلّ شيءٍ أبكر.

الكونسول: نافذتك إلى الشيفرة

قبل أن تكتب سطرًا، تعرّف إلى الأداة التي ستراها في كلّ مثال. افتح أدوات المطوّر في متصفّحك بالمفتاح F12، واختر تبويب Console. هذه لوحة تصحيح الأخطاء، ويصل إليها الكائن console في شيفرتك.[10] فالسطر console.log('مرحبًا') يطبع الكلمة فيها، وconsole.warn() يطبع رسالة تحذيرٍ بلونٍ مميّز.

وفي الكونسول نفسها تظهر أخطاء شيفرتك باللون الأحمر، ومعها نوع الخطأ واسم الملفّ ورقم السطر. وأوّل عادةٍ تنفعك أن تقرأ الرسالة الحمراء كاملةً قبل أن تغيّر شيئًا، فهي تدلّك في أكثر الأحيان على السطر الذي تبحث عنه. وإليك خطأً ستراه غالبًا في أوّل أسبوع. ضع سكربت المشروع الذي ستكتبه في آخر هذا المقال داخل head من غير defer، ثمّ افتح الصفحة. سيطبع Chrome في الكونسول: TypeError: Cannot read properties of null (reading 'classList')، ويشير إلى السطر 4 من script.js. فالشيفرة طلبت body قبل أن يقرأه المتصفّح، فوجدت مكانه القيمة null، وهي لا تملك classList. وحين تضيف defer يختفي الخطأ، والرسالة هنا منسوخةٌ كما يطبعها Chrome في الحالتين.

وتكتب التعليقات بعد // لسطرٍ واحد، أو بين /* و*/ لعدّة أسطر، ويتجاهلها المتصفّح كما في CSS.

المتغيّرات والقيم

المتغيّر اسمٌ تعطيه لقيمةٍ لتستعملها لاحقًا، وتكتبه اليوم بإحدى كلمتين: الأولى const، وهي لقيمةٍ لن تعيد تعيينها، وابدأ بها دائمًا لأنّها تمنعك من تغيير شيءٍ عن غير قصد. والثانية let، وهي لقيمةٍ ستتغيّر:

const blogName = 'مدوّنتي التقنيّة';
let visitorCount = 10;
visitorCount = visitorCount + 1;

وستقرأ في الشيفرات القديمة كلمةً ثالثة هي var. وتقول MDN إنّها تعرّف متغيّرًا على مستوى الدالّة كلّها أو على مستوى الصفحة، لا على مستوى الكتلة التي كُتب فيها، وهذا مصدر أخطاءٍ يصعب تتبّعها.[11] فلا تستعملها في شيفرةٍ جديدة، واعرفها حين تقرؤها فقط.

وأهمّ أنواع القيم للمبتدئ ثلاثة. النصّ، ويُكتب بين علامتي اقتباس مثل 'مرحبًا'، والرقم صحيحًا أو عشريًّا مثل 2026 أو 1.5، والقيمة المنطقيّة وهي true أو false فقط، وعليها تقوم كلّ القرارات في الشيفرة. وستلتقي أيضًا null، وتعني أنّ القيمة فارغةٌ عن قصد، وundefined، وتعني أنّها لم تُعطَ قيمةً بعد.

وحين تريد نصًّا فيه قيمة متغيّر، فأوضح طريقةٍ القالب النصّيّ. تكتب النصّ بين علامتي backtick بدل علامات الاقتباس، وتضع المتغيّر داخله بين ${ و}:[12]

const name = 'سلمى';
console.log(`أهلًا يا ${name}`);

وهذه العلامة يسمّيها بعض الشروح العربيّة «شرطةً مائلةً خلفيّة»، وهذا اسم علامةٍ أخرى هي \. والعلامة هنا تجدها على المفتاح الذي يسار الرقم 1، وهو في لوحة المفاتيح العربيّة على Windows يكتب حرف الذال، فبدّل إلى الإنكليزيّة لتكتبها.[12]

القرارات والتكرار

العوامل رموزٌ تعمل على القيم. منها الحسابيّة مثل + و- و* و/، ومنها عوامل المقارنة التي تعطيك true أو false. وأهمّها للمبتدئ عامل المساواة الصارم ===، فهو يعدّ القيمتين مختلفتين دائمًا إن اختلف نوعهما.[13] فالنصّ '5' والرقم 5 غير متساويين عنده، ولا يحوّل أحدهما إلى الآخر من وراء ظهرك كما يفعل العامل ==. فاستعمله في كلّ مقارنة.

وبالمقارنة تبني القرار، بالكلمتين if وelse: إن تحقّق الشرط نُفّذ ما في القوسين الأوّلين، وإلّا نُفّذ ما بعد else.

const hour = 21;

if (hour >= 18) {
  console.log('مساء الخير');
} else {
  console.log('صباح الخير');
}

ولحفظ مجموعةٍ من القيم أداتان: المصفوفة قائمةٌ مرتّبة بين قوسين مربّعين وأوّل عنصرٍ فيها رقمه 0، والكائن مجموعةٌ من الأزواج، اسمٌ وقيمة، بين قوسين معقوفين، ويصف شيئًا واحدًا بخصائصه:

const cities = ['دمشق', 'الرياض', 'برلين'];
console.log(cities[0]);

const reader = { name: 'سلمى', city: 'الرياض' };
console.log(reader.city);

وللمرور على عناصر المصفوفة كلّها طرق. أوضحها للمبتدئ الحلقة for…of، فهي تعطيك العنصر مباشرةً ولا تحتاج عدّادًا تديره أنت. وستقرأ أيضًا الحلقة الكلاسيكيّة for (let i = 0; i < cities.length; i++) والدالّة forEach، وثلاثتها تصل إلى النتيجة نفسها هنا:

for (const city of cities) {
  console.log(city);
}

الدوالّ: وصفةٌ لها اسم

الدالّة مجموعةٌ من الأوامر تعطيها اسمًا، ثمّ تنفّذها كلّما احتجت إليها باستدعاء اسمها. وتقبل الدالّة قيمًا تمرّرها إليها، اسمها المعاملات، وتعيد نتيجةً بالكلمة return:

function add(a, b) {
  return a + b;
}

const total = add(5, 3);

وستقرأ كثيرًا صيغةً أقصر اسمها الدالّة السهميّة، جاءت مع ES2015، وتكتب الدالّة نفسها في سطرٍ واحد: const add = (a, b) => a + b;. والصيغتان تؤدّيان الغرض نفسه في أمثلة هذا المقال، وستستعمل الثانية في المشروع حين تكتب ما يحدث عند النقر.

الصفحة شجرةٌ حيّة: DOM والأحداث

لغة JavaScript لا تعدّل ملفّ index.html على جهازك. فحين يقرأ المتصفّح الملفّ يبني منه في ذاكرته شجرةً اسمها DOM، وتصفها MDN بأنّها تمثيلٌ للمستند شجرةً منطقيّة، ينتهي كلّ فرعٍ منها بعقدة.[14] وهي الشجرة نفسها التي رأيتها مرسومةً لصفحة المشروع في مقال HTML. وشيفرتك تعمل على هذه الشجرة، فما تغيّره فيها يظهر على الشاشة فورًا، ويختفي حين تعيد تحميل الصفحة.

وأوّل خطوةٍ أن تجد العنصر. والدالّة document.querySelector() تأخذ محدِّد CSS نفسه الذي تعرفه، مثل '#theme-button' أو 'nav button'، وتعيد أوّل عنصرٍ يطابقه. والخطوة الثانية أن تنتظر فعل الزائر، وأفعاله تُسمّى أحداثًا. فالدالّة addEventListener() تضبط دالّةً يستدعيها المتصفّح كلّما وصل الحدث المحدّد إلى العنصر،[15] وأشهر الأحداث click للنقر، وinput للكتابة في حقل، وsubmit لإرسال نموذج، وkeydown لضغط مفتاح.

شكل 3 من النقرة إلى اللون خمس خطوات من اليمين إلى اليسار بأسهم: نقرةٌ على الزرّ هي الحدث، ثمّ المستمع addEventListener، ثمّ الدالّة applyTheme()، ثمّ الخطوة المميّزة: الصنف dark-mode على body، ثمّ تعيد CSS رسم الصفحة بالألوان الداكنة. نقرة الحدث addEventListener المستمع applyTheme() الدالّة body.dark-mode الصنف إعادة الرسم CSS من النقرة إلى اللون خمس خطوات من الأعلى إلى الأسفل بأسهم: نقرةٌ على الزرّ، ثمّ المستمع addEventListener، ثمّ الدالّة applyTheme()، ثمّ الخطوة المميّزة: الصنف dark-mode على body، ثمّ تعيد CSS رسم الصفحة. نقرة الحدث addEventListener المستمع applyTheme() الدالّة body.dark-mode الصنف إعادة الرسم CSS
ما سيحدث عند النقر في مشروع هذا المقال: الحدث يصل إلى المستمع، فيستدعي الدالّة applyTheme، فتضع الصنف dark-mode على body، ثمّ تعيد CSS رسم الصفحة بالألوان الداكنة.

وحين تغيّر نصّ عنصر، فاستعمل الخاصّيّة textContent. وستقرأ في شيفرةٍ كثيرة innerHTML للغرض نفسه، لكنّ MDN تنصح بعدم استعمالها للنصّ، لأنّها تتعامل مع HTML خامّ، وقد تفتح صفحتك لهجمات XSS إن وصل إليها نصٌّ كتبه زائر.[16] فالقاعدة بسيطة: textContent للنصّ دائمًا، وinnerHTML فقط حين تبني HTML تعرف مصدره.

أوّل تفاعل: زرّ القائمة

في مقال التصميم المتجاوب كتبت زرًّا للقائمة، وقاعدة CSS تُظهر القائمة حين تصير قيمة aria-expanded ‏true. وبقي سطرٌ يبدّل هذه القيمة عند النقر، وهذا هو:

const menuButton = document.querySelector('nav button');

menuButton.addEventListener('click', () => {
  const isOpen = menuButton.getAttribute('aria-expanded') === 'true';
  menuButton.setAttribute('aria-expanded', String(!isOpen));
});

اقرأه سطرًا سطرًا. الأوّل يجد الزرّ ويحفظه في const. والثاني يضبط دالّةً سهميّة تعمل عند كلّ نقرة، وداخلها يقرأ السطر الثالث قيمة السمة، وهي نصٌّ دائمًا، ويقارنه بالنصّ 'true' بالعامل ===، فيحفظ النتيجة في isOpen. والرابع يكتب القيمة المعاكسة، والرمز ! يقلب true إلى false والعكس، وString() تحوّلها نصًّا لأنّ السمات نصوص.

ولاحظ أنّ الشيفرة غيّرت سمةً واحدة فقط، وتركت الإظهار والإخفاء لـCSS. فمستخدم قارئ الشاشة يسمع أنّ القائمة مفتوحة من السمة نفسها التي فتحتها على الشاشة، وهذه طريقةٌ ستعيدها في المشروع بعد قليل: الشيفرة تغيّر حالة، وCSS تقرّر المظهر.

التطبيق: زرّ الوضع الداكن

المطلوب زرٌّ يبدّل صفحتك بين الفاتح والداكن، ويتذكّر اختيار الزائر في زيارته التالية. وإن لم يختر بعد يبدأ بما اختاره في نظامه، وتحتاج لذلك الملفّات الثلاثة: ففي index.html أضف سطر السكربت داخل head تحت سطر style.css، وأضف الزرّ في الترويسة تحت القائمة:

<script src="script.js" defer></script>

<button type="button" id="theme-button" aria-pressed="false">الوضع الداكن</button>

والسمة aria-pressed تخبر قارئ الشاشة أنّ هذا زرّ تبديل، له حالتان. ويشرح دليل ممارسات ARIA في W3C الخيار: إمّا أن يبقى نصّ الزرّ ثابتًا وتتغيّر aria-pressed، وإمّا أن يتغيّر النصّ نفسه مع الحالة فلا تحتاج إليها.[17] والطريقة الأولى أوضح للجميع، فالزرّ يقول دائمًا ما يفعله.

ثمّ أضف في آخر style.css هذه القواعد. وتذكّر الخاصّيّات المخصّصة التي عرّفتها في أوّل الملفّ في مقال CSS، فالوضع الداكن كلّه تغييرٌ لقيمها الثلاث:

#theme-button {
  margin-block-end: 1rem;
  padding-block: 0.5rem;
  padding-inline: 1rem;
  border: 1px solid var(--accent);
  border-radius: 0.5rem;
  background-color: transparent;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
}

#theme-button[aria-pressed="true"] {
  background-color: var(--accent);
  color: var(--paper);
}

body.dark-mode {
  --ink: #ece6da;
  --paper: #1b1a18;
  --accent: #e08a74;
}

body.dark-mode .card {
  border-color: #3a3733;
  background-color: #262421;
}

فحين يحمل body الصنف dark-mode تتغيّر الألوان الثلاثة، وتتبعها كلّ قاعدةٍ تقرؤها بالدالّة var(). وقاعدة الزرّ الثانية تملؤه باللون حين تكون aria-pressed ‏true، فيرى المبصر الحالة كما يسمعها مستخدم قارئ الشاشة. ثمّ أنشئ الملفّ script.js في المجلّد نفسه، واكتب فيه:

const themeButton = document.querySelector('#theme-button');

function applyTheme(isDark) {
  document.body.classList.toggle('dark-mode', isDark);
  themeButton.setAttribute('aria-pressed', String(isDark));
}

let savedTheme = null;
try {
  savedTheme = localStorage.getItem('theme');
} catch (error) {
  console.warn('تعذّرت قراءة الاختيار المحفوظ', error);
}

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
applyTheme(savedTheme ? savedTheme === 'dark' : prefersDark);

themeButton.addEventListener('click', () => {
  const isDark = !document.body.classList.contains('dark-mode');
  applyTheme(isDark);
  try {
    localStorage.setItem('theme', isDark ? 'dark' : 'light');
  } catch (error) {
    console.warn('تعذّر حفظ الاختيار', error);
  }
});
محرّر VS Code فيه مجلّد my-first-site بثلاثة ملفّات: index.html وscript.js وstyle.css، والملفّ script.js مفتوح في 26 سطرًا: متغيّر themeButton، ثمّ الدالّة applyTheme التي تبدّل الصنف dark-mode وتضبط aria-pressed، ثمّ قراءة الاختيار المحفوظ من localStorage داخل try وcatch، ثمّ matchMedia للوضع الداكن، ثمّ مستمع النقر على الزرّ.
الملفّ script.js لصفحة المشروع مفتوحًا في VS Code، وبجانبه في المستكشف index.html وstyle.css. لقطة شاشة من جهازي.

اقرأ الملفّ على أربعة أجزاء. الأوّل يجد الزرّ. والثاني دالّةٌ اسمها applyTheme تفعل شيئين: تضيف الصنف dark-mode إلى body أو تحذفه، وتضبط aria-pressed بالقيمة نفسها. والمعامل الثاني في classList.toggle() هو ما يجعل الدالّة تقبل قرارًا جاهزًا، فتقول MDN إنّه يحوّل التبديل إلى اتّجاهٍ واحد، إضافةً فقط أو حذفًا فقط.[18]

والجزء الثالث يقرّر الحالة عند فتح الصفحة. فهو يقرأ من localStorage ما حفظته زيارةٌ سابقة، وهو مخزنٌ صغير في المتصفّح لا تنتهي صلاحية بياناته. ووضعت القراءة داخل try وcatch لسبب: تذكر MDN أنّ المتصفّح قد يرمي الخطأ SecurityError حين يمنع الزائر الصفحات من حفظ البيانات.[19] فإن حدث ذلك طبعت الشيفرة تحذيرًا في الكونسول وأكملت عملها، بدل أن تتوقّف كلّها عند السطر الأوّل.

فإن لم تجد اختيارًا محفوظًا، سألت window.matchMedia() عن الشرط prefers-color-scheme الذي عرفته في مقال التصميم المتجاوب، فتعرف إن كان الزائر اختار الوضع الداكن في نظامه.[20] والسطر الذي يستدعي applyTheme يجمع القرار في عبارةٍ واحدة: إن وُجد اختيارٌ محفوظ فاتبعه، وإلّا فاتبع النظام.

والجزء الرابع مستمع النقر. فهو يحسب الحالة الجديدة عكس الحالة الحاليّة، ويطبّقها، ثمّ يحفظها في localStorage بالكلمة dark أو light. احفظ الملفّات الثلاثة، وافتح الصفحة، وانقر الزرّ.

ستنقلب الألوان كلّها في لحظة، ويمتلئ الزرّ بلونه. ثمّ أعد تحميل الصفحة، وستبقى داكنة، لأنّ الشيفرة قرأت اختيارك من localStorage قبل أن تراها.

صفحة المدوّنة نفسها مرّتين في متصفّح Chrome. في اليمين قبل النقر: خلفيّةٌ كريميّة، وزرّ «الوضع الداكن» بإطارٍ أحمر رفيع، والبطاقات الثلاث بيضاء. وفي اليسار بعد النقر: خلفيّةٌ داكنة ونصٌّ فاتح، والزرّ نفسه ممتلئٌ باللون الأحمر الفاتح، والبطاقات داكنة.
صفحة المشروع في Chrome قبل النقر وبعده. في اليمين الوضع الفاتح والزرّ بإطارٍ رفيع، وفي اليسار الوضع الداكن والزرّ ممتلئٌ بلونه. لقطة شاشة من جهازي.

وقد تلاحظ ومضةً قصيرة بالألوان الفاتحة قبل أن تصير الصفحة داكنة، إن كان اختيارك المحفوظ داكنًا. وسببها defer نفسه: قد يرسم المتصفّح الصفحة قبل أن ينفّذ السكربت الذي ينتظر نهاية التحليل. وتختفي الومضة بسكربتٍ صغير يُكتب داخل head نفسه، وهذا تحسينٌ تتركه لاحقًا بعد أن تتقن الأساس.

المكتبات وأطر العمل

ما كتبته في هذا المقال يسمّيه المطوّرون JavaScript الخالصة، أي من غير أدواتٍ جاهزة. وفي المشاريع الكبيرة يستعين أكثرهم بمكتبات وأطر عمل مثل React وVue وAngular، تبني الواجهة من مكوّناتٍ صغيرة وتدير حالتها بدلًا منك، وستتعرّف إليها في مقالها الخاصّ في هذه السلسلة.

ونصيحتي ألّا تقفز إليها قبل أن تكتب بيدك ما كتبته هنا: أن تجد عنصرًا، وتستمع إلى حدث، وتغيّر حالة، وتترك لـCSS المظهر. فكلّ إطارٍ منها مكتوبٌ بهذه اللغة، ومن فهم querySelector وaddEventListener وclassList يقرأ الإطار كما يُقرأ الكتاب، ومن بدأ بالإطار يحفظ أوامره بلا فهم.

خلاصة عمليّة

اربط سكربتك في head بالسمة defer. افتح الكونسول بالمفتاح F12 واقرأ الخطأ الأحمر كاملًا قبل أن تغيّر شيئًا. اكتب const أوّلًا، وlet حين تتغيّر القيمة، ولا تكتب var. قارن بالعامل ===، واكتب القوالب النصّيّة بعلامة backtick من لوحة المفاتيح الإنكليزيّة. غيّر النصّ بـtextContent، وضع كلّ ما يلمس localStorage داخل try وcatch. واجعل شيفرتك تغيّر حالةً، صنفًا أو سمةً مثل aria-pressed، واترك المظهر لـCSS.

وصفحتك الآن تستجيب للزائر، وتتذكّر اختياره، ويسمع مستخدم قارئ الشاشة حالة أزرارها. ويبقى سؤالٌ أكبر من زرّين: هل يستطيع كلّ زائرٍ أن يستعمل صفحتك كلّها، بلوحة المفاتيح وحدها، أو بقارئ شاشة، أو بنظرٍ ضعيف؟ وهذا ما يسمّى إتاحة الويب، وهو موضوع المقال التالي.

المصادر

  1. موقع W3Techs، «Usage statistics of JavaScript as client-side programming language on websites»، قُرئ في 30 أيلول 2026: تستعمل 98.9% من المواقع JavaScript في المتصفّح.
  2. موقع Stack Overflow، «2025 Developer Survey: Technology»: JavaScript أكثر اللغات استعمالًا بين كلّ المشاركين، 66%.
  3. موسوعة MDN، «What is JavaScript?»: لغة برمجةٍ تتيح لك أن تضيف إلى صفحات الويب ميزاتٍ معقّدة، في كلّ مرّةٍ تفعل فيها الصفحة أكثر من أن تبقى ثابتة.
  4. محرّكات JavaScript: موقع V8 (يُستعمل في Chrome وNode.js)، وموقع SpiderMonkey (محرّك Mozilla في Firefox)، وتوثيق WebKit عن JavaScriptCore، المحرّك المدمج في WebKit.
  5. ألن ويرفس-بروك وبريندان آيك، «JavaScript: The First 20 Years»، مؤتمر HOPL-4، حزيران 2020 (نسخة المؤلّفَين): صُمّمت اللغة ونُفّذت في Netscape في أيّار 1995 في نموذجٍ أوّليّ استغرق عشرة أيّام، باسم Mocha ثمّ LiveScript في النسخة التجريبيّة من Netscape Navigator 2.0 في أيلول 1995، ثمّ أُعلن اسم JavaScript في 4 كانون الأوّل 1995. وكتبت مايكروسوفت نسختها JScript بالهندسة العكسيّة، وبدأ توحيد اللغة في Ecma سنة 1996، وصدرت الطبعة الأولى من المعيار في حزيران 1997، والسادسة ES2015 في حزيران 2015. وكان رايان دال يطوّر Node.js في مطلع 2009.
  6. شركة مايكروسوفت، «Visual Studio Code FAQ»: يعمل VS Code على غلاف Electron.
  7. صورة «Brendan Eich Mozilla Foundation official photo» لدارسي باديا، 2012، برخصة CC BY-SA 3.0، وشعار JS على غلاف المقال من مشروع logo.js لكريستوفر ويليامز برخصة MIT.
  8. منظّمة Ecma International، «ECMA-262: ECMAScript 2026 language specification»: الطبعة السابعة عشرة، حزيران 2026، وقبلها طبعةٌ كلّ حزيران.
  9. موسوعة MDN، «The script element»: السمة defer تجعل السكربت يعمل بعد تحليل المستند وقبل الحدث DOMContentLoaded، والسكربت بلا defer ولا async ولا type="module" يوقف تحليل الصفحة.
  10. موسوعة MDN، «console»: الكائن console يصل إلى لوحة تصحيح الأخطاء في المتصفّح، وconsole.warn() يطبع رسالةً بمستوى التحذير.
  11. موسوعة MDN، «var»: يعرّف var متغيّراتٍ على مستوى الدالّة أو على المستوى العامّ.
  12. موسوعة MDN، «Template literals»: تُحاط القوالب النصّيّة بعلامة backtick بدل علامات الاقتباس. وموقع kbdlayout.info، «Arabic (101)»: المفتاح الذي على يسار الرقم 1 يكتب في هذا التخطيط حرف الذال.
  13. موسوعة MDN، «Strict equality (===)»: يعدّ القيمتين مختلفتين دائمًا إن اختلف نوعهما، بخلاف عامل المساواة العاديّ.
  14. موسوعة MDN، «Introduction to the DOM»: يمثّل DOM المستند شجرةً منطقيّة، ينتهي كلّ فرعٍ منها بعقدة.
  15. موسوعة MDN، «addEventListener()»: يضبط دالّةً تُستدعى كلّما وصل الحدث المحدّد إلى العنصر.
  16. موسوعة MDN، «textContent»: لا يُنصح باستعمال innerHTML لقراءة النصّ أو كتابته لأنّه يتعامل مع HTML خامّ وقد يعرّض الصفحة لهجمات XSS.
  17. اتّحاد W3C، «Button Pattern»، دليل ممارسات ARIA: زرّ التبديل يُعلَن بالسمة aria-pressed، أمّا إن تغيّر نصّ الزرّ نفسه مع الحالة فلا حاجة إليها.
  18. موسوعة MDN، «DOMTokenList.toggle()»: المعامل الثاني force يجعل التبديل في اتّجاهٍ واحد، إضافةً فقط أو حذفًا فقط.
  19. موسوعة MDN، «Window.localStorage»: بيانات localStorage لا تنتهي صلاحيتها، وقد يرمي المتصفّح الخطأ SecurityError حين يمنع المستخدم الصفحة من حفظ البيانات.
  20. موسوعة MDN، «Window.matchMedia()»: يعيد كائن MediaQueryList يخبرك إن كانت الصفحة تطابق استعلام وسائط.

اترك تعليقاً

الحقول المميّزة بـ مطلوبة