مقدمة: لماذا التعامل مع الملفات في JavaScript أمر لا غنى عنه
\n\nتخيل أنك قائد سفينة فضائية. مهمتك هي إدارة مخازن الشحن وخزانات الأكسجين وسجل الرحلة. في عالم تطوير الويب، نظام الملفات هو تلك المخازن، وJavaScript هي لوحة التحكم الخاصة بك. بدون مهارة التعامل مع الملفات، ستبقى عالقًا في المدار: لن تتمكن من حفظ إعدادات المستخدم، أو تحميل إعدادات الخادم، أو معالجة ملفات السجلات.
\n\nيعتقد الكثير من المبتدئين أن JavaScript تعيش فقط في المتصفح ولا تستطيع لمس الملفات على القرص الصلب. هذه خرافة! مع ظهور Node.js (وحتى في المتصفحات الحديثة عبر File API)، أصبحت JavaScript أداة كاملة للتعامل مع نظام الملفات. اليوم سنتعلم كيفية قراءة وكتابة وحذف ونقل الملفات، وكيفية تجنب فخاخ البرمجة غير المتزامنة.
\n\nبالمناسبة، إذا كنت في بداية رحلتك مع البرمجة، أنصحك أولاً بدراسة أساسيات JavaScript — سيساعدك ذلك على فهم الأمثلة أدناه بشكل أسرع.
\n\nأين يعمل JavaScript مع الملفات؟ عالمين — نهجان مختلفان
\n\nقبل الغوص في الكود، دعنا نوضح: عمليات الملفات في JS تنقسم إلى معسكرين.
\n\n1. JavaScript من جهة الخادم (Node.js)
\nهنا لدينا وصول كامل إلى نظام الملفات: إنشاء، حذف، إعادة تسمية، قراءة التدفقات — كل ما تشتهيه النفس. نعمل من خلال الوحدة المدمجة fs (File System). إنها مثل سكين سويسري للتعامل مع الملفات.
2. JavaScript من جهة العميل (المتصفح)
\nالمتصفح هو "صندوق رمل" به قيود. لا يمكنك ببساطة قراءة أي ملف من جهاز المستخدم (وإلا لكان المخترقون يحتفلون). ولكن يمكنك العمل مع الملفات التي يختارها المستخدم بنفسه عبر <input type="file">، أو عبر واجهات FileReader و Blob.
في هذه المقالة، سنركز على Node.js، لكن في النهاية سنقدم مكافأة — مثال للمتصفح.
\n\nوحدة fs: أداتك الرئيسية
\n\nفي Node.js، يبدأ التعامل مع الملفات باستيراد الوحدة fs:
const fs = require('fs'); // الطريقة القديمة\n// أو\nimport fs from 'fs'; // إذا كنت تستخدم وحدات ES\n\nلـ fs ثلاثة أوضاع عمل: متزامن، غير متزامن مع دوال الاسترجاع، وغير متزامن مع Promises. سنستخدم الأخير مع async/await، لأنه نهج حديث وسهل القراءة.
قراءة ملف: من البسيط إلى المعقد
\n\nلنفترض أن لدينا ملف data.txt يحتوي على نص. أسهل طريقة لقراءته:
import { readFile } from 'fs/promises';\n\nasync function readMyFile() {\n try {\n const data = await readFile('data.txt', 'utf-8');\n console.log(data);\n } catch (err) {\n console.error('خطأ في القراءة:', err.message);\n }\n}\n\nreadMyFile();\n\nما المهم هنا؟ 'utf-8' هو ترميز النص. إذا لم تحدده، ستحصل على Buffer خام يجب تحويله إلى نص. للملفات النصية، حدد الترميز دائمًا.
إذا كان الملف كبيرًا (مثل ملف سجل بحجم 500 ميجابايت)، فإن قراءته بالكامل فكرة سيئة. هنا تأتي التدفقات (streams) للإنقاذ:
\n\nimport { createReadStream } from 'fs';\nimport { createInterface } from 'readline';\n\nasync function readLargeFile() {\n const stream = createReadStream('bigfile.log', 'utf-8');\n const rl = createInterface({ input: stream });\n\n for await (const line of rl) {\n console.log('سطر:', line);\n // معالجة كل سطر أثناء القراءة\n }\n}\n\nreadLargeFile();\n\nالكتابة في ملف: إنشاء وإضافة
\n\nالكتابة نوعان: استبدال (write) وإضافة (append). الفرق واضح: write سيمسح الملف القديم، append سيضيف في النهاية.
import { writeFile, appendFile } from 'fs/promises';\n\n//