האצת פירוס של JSON גדול
שורה תחתונה: אופטימיזציה בסדר הלא נכון הופכת כל החלפת ספרייה לחסרת תועלת. מדדו רשת, פירוס ורינדור בנפרד, ואז צמצמו פירוסים, כווצו payloads ועברו ל-streaming.

שורה תחתונה: מדדו קודם בנפרד רשת, פירוס ורינדור — רוב "הפירוס האיטי" מתגלה כרינדור איטי — ואז טפסו בסולם לפי הסדר: פחות פירוסים, payloads קטנים יותר, streaming, 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();
| שלב דומיננטי | הבעיה האמיתית | התיקון |
|---|---|---|
| רשת | payload גדול מדי | בחירת שדות, pagination, דחיסה |
JSON.parse |
קלט גדול באמת | פחות פירוסים, streaming, worker |
| רינדור | עשרות אלפי צמתים בבת אחת | רשימה וירטואלית, עימוד, כיווץ |
דפדפנים מודרניים מפרסים 1MB בכ-5–15ms ו-10MB ב-80–150ms. אם 10MB לוקח שתי שניות, JSON.parse הוא לא צוואר הבקבוק שלכם.
שלב שני: פרסו פחות
הניצחון הזול ביותר, והנשכח ביותר.
- הפסיקו לפרס את אותו payload שוב ושוב. עשרה רכיבים שכל אחד קורא ל-
JSON.parse(localStorage.getItem(...))הם עשרה פירוסים. עשו cache פעם אחת ברמת המודול. - הפסיקו להשתמש ב-
JSON.parse(JSON.stringify(x))להעתקה עמוקה. זה עולה יותר מפי שניים מנפח הנתונים;structuredCloneלא. - אל תפרסו ב-main thread ואז תשלחו את האובייקט ל-worker.
postMessageמשכפל אותו שוב. שלחו את הטקסט הגולמי ותנו ל-worker לפרס. - בקשו פחות שדות. אם ה-UI צריך שלושה מאפיינים, ה-API לא צריך לשלוח שלוש מאות.
שלב שלישי: כווצו את ה-payload
Pagination לפי page/size, cursors במקום offset עמוק, שדות סיכום בנקודות קצה של רשימות. זה בדרך כלל מנצח כל אופטימיזציית פרונט משולבת, כי הוא מקטין בבת אחת רשת, פירוס וזיכרון.
שלב רביעי: streaming
כשהנתונים חייבים להיות מעובדים כמכלול והם באמת גדולים:
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);
}
יותר קוד, ואין גישה אקראית — מתאים רק לעיבוד רשומה-אחר-רשומה.
שלב חמישי: העבירו אותו מה-main thread
פירוס מהיר אבל חוסם. שימו אותו ב-worker והעבירו את הטקסט, לא את האובייקט המפורס, אחרת structured cloning מכפיל את העלות.
שלב שישי: שנו את הפורמט, אחרון
לתנועה פנימית שבאופן עקבי מגיעה לעשרות מגה-בייט, NDJSON זורם באופן טבעי, ו-Protobuf או MessagePack קטנים ומהירים יותר. שניהם שינויי פרוטוקול שדורשים תיאום.
אל תעשו
evalעל JSON: לא בטוח ולא מהיר יותר- המרת JSON למחרוזת כדי להטמיע אותו ב-JSON: מכפילה את הגודל
- פירוס של מאות אלפי מחרוזות זעירות בלולאה: אגדו אותן בקבוצות
שאלות ותשובות
איך יודעים אם הפירוס או הרינדור הוא צוואר הבקבוק?
תזמנו בנפרד את הבקשה, קריאת הטקסט, JSON.parse והרינדור עם performance.now(), והתייחסו לשלב הגדול ביותר כאל צוואר הבקבוק האמיתי. דפדפנים מודרניים מפרסים 1MB בכ-5–15ms ו-10MB ב-80–150ms, כך ש-payload של 10MB שלוקח שתי שניות הוא לעיתים רחוקות בעיית JSON.parse.
למה החלפת ספריות בדרך כלל לא עוזרת?
JSON.parse הוא כבר נייטיבי ומהיר; רוב "הפירוס האיטי" נובע מפירוס חוזר או מרינדור של עשרות אלפי צמתים בבת אחת. החלפת ספרייה קונה 10–30%, בעוד שהסרת פירוס מלא אחד חוסכת לעיתים קרובות חצי.
האם להעביר את הפירוס ל-Web Worker?
העבירו אותו ל-worker כשהפירוס חוסם את ה-main thread והדף קופא או שהאנימציות מאבדות פריימים. העבירו את הטקסט הגולמי ולא את האובייקט המפורס, אחרת structured cloning של postMessage מכפיל את העלות.