عندما لا يكون المتصفح لعبة، بل مركز بيانات
\n\nلقد اعتدنا على أن جافا سكريبت يعيش في صندوق رمل المتصفح: الرسوم المتحركة، النقرات، النماذج. ولكن ماذا لو أخبرتك أنه باستخدام JS الخالص يمكنك معالجة ملف CSV بحجم 500 ميجابايت، ورسم بياني بـ 100,000 نقطة دون تعليق علامة التبويب؟ هذا ليس خيالاً علمياً، بل استخدام صحيح لـ معالجة البيانات الضخمة على جافا سكريبت.
\nفي هذه المقالة، سأوضح لك كيفية العمل مع كميات كبيرة من البيانات مباشرة في المتصفح - بدون خادم خلفي، بدون Python، بدون نسخ الجيجابايتات إلى الخادم. ستتعرف على البث المباشر، Web Workers، تحسين الذاكرة، والمكتبات التي تحول المتصفح إلى معالج بيانات مصغر. هيا بنا!
\n\nلماذا تقتل المعالجة "الساذجة" للبيانات الأداء
\nتخيل: تقوم بتحميل ملف CSV بحجم 300 ميجابايت وتكتب:
\nconst data = await response.json(); // ❌ خطأ فادح\nماذا يحدث؟ يحاول JS تحليل الملف بأكمله في الذاكرة العاملة. إذا كان لدى المستخدم 4 جيجابايت من ذاكرة الوصول العشوائي - سينهار المتصفح. حتى على جهاز قوي، سيبدأ جامع القمامة في "التباطؤ"، وسيتجمد الواجهة لمدة دقيقة.
\nالقاعدة رقم 1: لا تقم بتحميل كل شيء دفعة واحدة. تتطلب البيانات الضخمة على جافا سكريبت معالجة تدفقية (streaming) وتقسيمها إلى أجزاء.
\n\nبث البيانات: قراءة الملف قطعة قطعة
\nتسمح واجهة برمجة تطبيقات المتصفح FileReader بقراءة الملف على أجزاء. ولكن الأكثر ملاءمة هو ReadableStream. مثال: معالجة ملف JSON كبير سطراً بسطر.
مثال: بث JSON باستخدام NDJSON
\nتنسيق NDJSON (كل سطر هو كائن JSON منفصل) مثالي للبيانات الضخمة. إليك كيفية قراءته كتيار:
\nasync function* streamJSON(file) {\n const stream = file.stream();\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop(); // الباقي في المخزن المؤقت\n for (const line of lines) {\n if (line.trim()) yield JSON.parse(line);\n }\n }\n}\n\n// الاستخدام\nfor await (const obj of streamJSON(selectedFile)) {\n processRow(obj); // معالجة كائن واحد في كل مرة\n}\n\nالذاكرة لا تكاد تزيد - نحتفظ بسطر واحد فقط في الذاكرة. هذا النهج هو أساس معالجة البيانات الضخمة باستخدام جافا سكريبت في المشاريع الحقيقية.
\n\nWeb Workers: لا نعلق الواجهة
\nحتى البث المباشر يتم تنفيذه في الخيط الرئيسي. إذا كانت الحسابات معقدة - ستتجمد واجهة المستخدم. الحل: Web Workers. هذا خيط منفصل حيث يمكنك معالجة الجيجابايتات دون تأخير.
\n\nإنشاء Worker لمعالجة CSV
\nكود العامل (worker.js):
\nself.onmessage = async function(e) {\n const { file, chunkSize } = e.data;\n const reader = new FileReaderSync(); // قراءة متزامنة - مسموح للعامل\n let offset = 0;\n const results = [];\n\n while (offset < file.size) {\n const blob = file.slice(offset, offset + chunkSize);\n const text = reader.readAsText(blob);\n const lines = text.split('\\n');\n for (const line of lines) {\n if (line.trim()) results.push(parseCSVLine(line));\n }\n offset += chunkSize;\n // إرسال النتيجة الوسيطة\n self.postMessage({ type: 'chunk', data: results.splice(0, results.length) });\n }\n self.postMessage({ type: 'done' });\n};\n\nfunction parseCSVLine(line) {\n const [name, age, city] = line.split(',');\n return { name, age: parseInt(age), city };\n}\n\nالخيط الرئيسي:
\nconst worker = new Worker('worker.js');\nworker.postMessage({ file: csvFile, chunkSize: 1024 * 1024 }); // أجزاء بحجم 1 ميجابايت\n\nworker.onmessage = (e) => {\n if (e.data.type === 'chunk') {\n updateUI(e.data.data); // تحديث الجدول/الرسم البياني بسلاسة\n }\n};\n\nالآن تبقى الواجهة سريعة الاستجابة، حتى لو كنا نعالج 2 جيجابايت من البيانات. هذه هي التقنية الأساسية لـ b