تسريع تحليل JSON الكبير
الخلاصة: التحسين بترتيب خاطئ يجعل كل تبديل مكتبة بلا فائدة. قس الشبكة والتحليل والعرض كلًا على حدة، ثم قلّل التحليلات وقلّص الحمولة واستعمل البث.

الخلاصة: قِس الشبكة والتحليل والعرض كلًا على حدة أولًا — فأغلب حالات «التحليل البطيء» تكون عرضًا بطيئًا — ثم اصعد السلّم بالترتيب: تحليلات أقل، وحمولات أصغر، والبث، وworker، والتنسيق الثنائي أخيرًا.
لا تبدأ بتبديل المكتبات. أضف مؤقتًا واحدًا وستجد عادةً أن العرض هو المشكلة.
الخطوة الأولى: قِس المراحل
const t0 = performance.now();
const response = await fetch(url);
const text = await response.text();
const t1 = performance.now();
const data = JSON.parse(text);
const t2 = performance.now();
render(data);
const t3 = performance.now();
| المرحلة المهيمنة | المشكلة الحقيقية | الإصلاح |
|---|---|---|
| الشبكة | الحمولة كبيرة جدًا | اختيار الحقول، والترقيم، والضغط |
JSON.parse |
مدخل كبير فعلًا | تحليلات أقل، والبث، وworker |
| العرض | عشرات آلاف العقد دفعة واحدة | قائمة افتراضية، وتقسيم صفحات، وطي |
تحلل المتصفحات الحديثة 1MB في نحو 5–15ms و10MB في 80–150ms. فإن استغرق 10MB ثانيتين، فليس JSON.parse عنق زجاجك.
الخطوة الثانية: حلّل أقل
أرخص مكسب، وأكثره إغفالًا.
- كفّ عن تحليل الحمولة نفسها مرارًا. عشرة مكونات يستدعي كل منها
JSON.parse(localStorage.getItem(...))تعني عشرة تحليلات. خزّن مؤقتًا مرة واحدة على مستوى الوحدة. - كفّ عن استخدام
JSON.parse(JSON.stringify(x))للنسخ العميق. يكلف أكثر من ضعف حجم البيانات؛ وstructuredCloneلا يفعل ذلك. - لا تحلّل على الخيط الرئيسي ثم ترسل الكائن إلى worker.
postMessageيستنسخه مرة أخرى. أرسل النص الخام ودع worker يحلله. - اطلب حقولًا أقل. إن كانت الواجهة تحتاج ثلاث خصائص فلا يجب أن يرسل API ثلاثمئة.
الخطوة الثالثة: قلّص الحمولة
الترقيم بـpage/size، والمؤشرات بدلًا من offset العميق، وحقول الملخص في نقاط نهاية القوائم. هذا يتغلب عادةً على كل تحسينات الواجهة مجتمعة، لأنه يقلل الشبكة والتحليل والذاكرة معًا.
الخطوة الرابعة: استخدم البث
عندما يجب معالجة البيانات ككتلة واحدة وتكون كبيرة فعلًا:
import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray';
const pipeline = fs.createReadStream('big.json').pipe(parser()).pipe(streamArray());
for await (const { value } of pipeline) {
await handleOne(value);
}
سطور أكثر، ولا وصول عشوائي — مناسب فقط للمعالجة سجلًا بسجل.
الخطوة الخامسة: انقله خارج الخيط الرئيسي
التحليل سريع لكنه حاجز. ضعه في worker ومرّر النص، لا الكائن المحلل، وإلا فإن الاستنساخ المهيكل يضاعف التكلفة.
الخطوة السادسة: غيّر التنسيق، أخيرًا
للحركة الداخلية التي تبلغ عشرات الميغابايت باستمرار، يتدفق NDJSON طبيعيًا، وProtobuf أو MessagePack أصغر وأسرع. كلاهما تغيير بروتوكول يتطلب تنسيقًا.
ما لا تفعله
evalعلى JSON: غير آمن وليس أسرع- تحويل JSON إلى سلسلة لتضمينه في JSON: يضاعف الحجم
- تحليل مئات الآلاف من السلاسل الصغيرة في حلقة: اجمعها في دفعات
الأسئلة الشائعة
كيف أعرف هل عنق الزجاجة هو التحليل أم العرض؟
وقّت الطلب، وقراءة النص، وJSON.parse، والعرض كلًا على حدة بـperformance.now()، واعتبر أكبر مرحلة هي عنق الزجاجة الحقيقي. تحلل المتصفحات الحديثة 1MB في نحو 5–15ms و10MB في 80–150ms، لذا فحمولة 10MB تستغرق ثانيتين نادرًا ما تكون مشكلة JSON.parse.
لماذا لا يفيد تبديل المكتبات عادةً؟
JSON.parse أصلي وسريع بالفعل؛ وأغلب «التحليل البطيء» يأتي من تحليل متكرر أو عرض عشرات آلاف العقد دفعة واحدة. تبديل مكتبة يجلب 10–30%، بينما إزالة تحليل كامل واحد توفر غالبًا النصف.
هل أنقل التحليل إلى Web Worker؟
انقله إلى worker عندما يحجب التحليل الخيط الرئيسي فتتجمد الصفحة أو تفقد الحركات إطاراتها. مرّر النص الخام لا الكائن المحلل، وإلا فإن الاستنساخ المهيكل لـpostMessage يضاعف التكلفة.