اگر روزهای خود را صرف کدنویسی میکنید، ساعتهای خود را در دو محیط سپری میکنید: پنجره مرورگر که کار شما در واقع در آن اجرا میشود، و مخزن Git که هر تصمیمی را که برای رسیدن به آن نقطه گرفتهاید، به خاطر میسپارد. یکی رو به عموم و غیرقابل پیشبینی است، و دیگری خصوصی و دقیق. درک هر دو الزامی است. تسلط بر مکانیزمهای داخلی مرورگر و منطق staging در Git، مرز بین توسعهدهندگانی است که حدس میزنند و کسانی که دقیقاً میدانند چرا چیزی خراب شده و چه زمانی تغییر کرده است.
آناتومی URL
هر مراجعه به یک وبسایت با رشتهای از کاراکترها شروع میشود که ساده به نظر میرسد اما دستورالعملهای دقیقی را با خود حمل میکند. یک URL مانند https://shop.example.com:443/products/id/42?sort=price#reviews در واقع مجموعهای از دستورالعملهای مجزا است.
protocol در ابتدا قرار میگیرد و قوانین گفتگو را تعیین میکند. وقتی https:// را میبینید، مرورگر میداند که باید قبل از ارسال هر چیزی، اتصال را رمزنگاری کند. domain (shop.example.com) نامی قابل خواندن برای انسان از آدرس شبکه واقعی سرور است. این نام از طریق DNS حل (resolve) میشود تا کامپیوتر شما بداند کجا باید درخواست بفرستد. port (:443) درگاه خاصی در آن سرور است. این بخش اغلب نامرئی است زیرا مرورگرها برای HTTPS فرض را بر ۴۴۳ و برای HTTP بر ۸۰ میگذارند، اما در مکانیزمها همیشه وجود دارد. path (/products/id/42) به سرور میگوید که شما کدام منبع را میخواهید، که مانند پوشهها سازماندهی شده است. query string (?sort=price) دادههای پویا را به صورت جفتهای کلید-مقدار (key-value pairs) تحویل میدهد که برای فیلترها، عبارات جستجو یا صفحهبندی (pagination) عالی است. در نهایت، fragment (#reviews) به یک ID خاص از یک المان در صفحه اشاره میکند. این بخش هرگز به سرور نمیرسد؛ مرورگر آن را کاملاً در سمت کلاینت و پس از رسیدن صفحه مدیریت میکند.
قطعه (fragment) را در انتها نگه دارید. اگر آن را قبل از query string قرار دهید، لینک خراب میشود زیرا هر چیزی که بعد از علامت hash قرار میگیرد، به عنوان بافت (context) سمت کلاینت در نظر گرفته میشود، نه دستورالعمل سرور.
DOM: سیستم عصبی زنده صفحه شما
HTML که از طریق شبکه میرسد، صرفاً متن است. مرورگر آن متن را میخواند و Document Object Model را میسازد؛ نقشهای زنده و درختی از اشیایی به نام nodes (گرهها). تگهای المان به element node تبدیل میشوند. متن بین تگها به text node تبدیل میشود. حتی ویژگیها (attributes) و کامنتها نیز انواع node مخصوص به خود را دارند. این درخت یک نمودار ایستا نیست، بلکه یک ساختار داده زنده است که JavaScript میتواند در لحظه آن را بخواند و بازنویسی کند.
وقتی اسکریپت شما document.getElementById را اجرا میکند یا یک className را تغییر میدهد، در واقع در حال دسترسی به این درخت و تغییر دادن (mutate) آن هستید. مرورگر متوجه شده و صفحه را بدون درخواست صفحه جدید از سرور، دوباره ترسیم (repaint) میکند. این قدرت همان چیزی است که اپلیکیشنهای وب مدرن را ممکن میسازد، اما هزینهای هم دارد. هر بار که به DOM دست میزنید، مرورگر ممکن است چیدمان (layout) و استایلها را دوباره محاسبه کند. اگر این کار را داخل یک حلقه فشرده با صدها آیتم انجام دهید، نرخ فریم (frame rate) شما به شدت افت خواهد کرد. اگر نیاز به درج یک لیست طولانی دارید، ابتدا یک DocumentFragment در حافظه بسازید و سپس آن را یکبار اضافه (append) کنید. خواندنها و نوشتنهای خود را دستهای (batch) انجام دهید. DOM منعطف است، اما رایگان نیست.
ذخیرهسازی مرورگر: سه ابزار، سه وظیفه
مرورگرهای مدرن به شما اجازه میدهند دادهها را مستقیماً روی دستگاه کاربر ذخیره کنید، و انتخاب مکانیزم مناسب اهمیت دارد زیرا هر کدام برای طول عمر و ظرفیت متفاوتی ساخته شدهاند.
LocalStorage سادهترین است. مقادیر کمی از دادههای رشتهای را تا زمانی که کد شما یا کاربر آن را حذف کند، به صورت دائمی ذخیره میکند. یک مورد استفاده کلاسیک، تنظیمات حالت تاریک (dark-mode) است. وقتی کسی سوئیچ را تغییر میدهد، "theme": "dark" را در LocalStorage بنویسید. در بازدید بعدی، آن را دوباره بخوانید و قبل از اولین ترسیم (paint)، کلاس مربوطه را اعمال کنید. این ذخیرهساز همگام (synchronous) است و محدود به مبدأ (origin) میباشد، که باعث راحتی کار میشود اما به این معناست که هرگز نباید توکنهای حساس را در آن قرار دهید. هر اسکریپتی که روی صفحه شما در حال اجرا باشد، میتواند آن را بخواند.
SessionStorage از همان API کلید-مقدار استفاده میکند، اما طول عمر آن به تب مرورگر وابسته است. این ذخیرهساز در هنگام رفرش شدن صفحه باقی میماند، که آن را برای ذخیره پیشرفت موقت فرمها عالی میکند. تصور کنید کاربری در حال پر کردن یک نظرسنجی طولانی است، تصادفاً دکمه reload را میزند و همچنان پاسخهای خود را میبیند، چون شما آنها را در SessionStorage ذخیره کرده بودید. وقتی کاربر تب را میبندد، دادهها به طور خودکار پاک میشوند.
Cache API در مقیاس متفاوتی عمل میکند. این API جفتهای درخواست و پاسخ را ذخیره میکند که معمولاً توسط service workerها برای نگهداری داراییهای استاتیک بزرگ مانند تصاویر، فونتها و بستههای اسکریپت (script bundles) استفاده میشود. به جای اینکه در هر بازدید، همان تصویر اصلی (hero image) یا بسته React را از طریق شبکه دریافت کنید، اپلیکیشن شما میتواند آن را مستقیماً از حافظه کش دیسک (disk cache) ارائه دهد. این همان روشی است که سایتهای دارای قابلیت آفلاین، در بازدیدهای مجدد بهصورت آنی بارگذاری میشوند. این یک ذخیرهساز کلید-مقدار (key-value store) عمومی مانند دو مورد دیگر نیست؛ بلکه اختصاصاً برای پاسخهای HTTP ساخته شده است.
یک قانون سختگیرانه: هرگز توکنهای احراز هویت یا شناسههای شخصی را در LocalStorage ذخیره نکنید. حملات XSS میتوانند آنها را در عرض چند میلیثانیه سرقت کنند. برای هر چیز حساس، از کوکیهای HttpOnly ،Secure و SameSite استفاده کنید و آنها را در تب Application بررسی کنید تا مطمئن شوید پرچمها (flags) واقعاً تنظیم شدهاند.
ابزارهای توسعه مرورگر (Browser DevTools): حدس زدن را متوقف کنید، خواندن را شروع کنید
پنل DevTools فقط برای رفع خطاهای قرمز کنسول نیست. این آزمایشگاه تشخیص شما برای هر اتفاقی است که در داخل مرورگر رخ میدهد.
در پنل Elements، میتوانید روی درخت DOM نگه دارید و مشاهده کنید که گرهها (nodes) بهصورت لحظهای در صفحه هایلایت میشوند. میتوانید مقادیر CSS را مستقیماً در بخش Styles ویرایش کنید تا یک margin یا رنگ را قبل از دست زدن به کد منبع خود تست کنید. بخش Console یادداشتبرداری (scratchpad) شماست. اشیاء را لاگ کنید، regex را تست کنید یا توابع را بهصورت زنده در برابر وضعیت فعلی صفحه فراخوانی کنید. اگر متغیری درست عمل نمیکند، نام آن را تایپ کرده و مستقیماً آن را بررسی کنید.
تب Network حقیقت را درباره عملکرد (performance) آشکار میکند. آن صفحه کند ممکن است به خاطر جاوااسکریپت شما نباشد. ممکن است یک فونت شخص ثالث باشد که چهار ثانیه طول میکشد تا پاسخ دهد، یا یک نقطه پایانی API (endpoint) که یک بار داده JSON دو مگابایتی را برمیگرداند که هرگز آن را فشرده نکردهاید. میتوانید چرخه کامل عمر هر درخواست را ردیابی کنید، با فیلتر Fetch/XHR فراخوانیهای API خود را مشاهده کنید و هدرها را بررسی کنید تا ببینید آیا دستورات کشینگ (caching directives) رعایت میشوند یا خیر. در همین حال، تب Application به شما اجازه میدهد ذخیرهسازی خود را حسابرسی کنید. به جفتهای کلید-مقدار در LocalStorage نگاهی بیندازید، کوکیهای مجزا و پرچمهای آنها را بررسی کنید و تأیید کنید که service worker شما واقعاً ثبت شده و آنچه انتظار دارید را کش میکند.
گردش کار Git: سه ظرف (The Three Buckets)
Git نرمافزار پشتیبانگیری نیست؛ ابزاری برای مدیریت و سازماندهی تاریخچه است. فکر کردن به این صورت، نحوه استفاده شما از آن را تغییر میدهد. Git پروژه شما را از طریق سه ناحیه متمایز مدیریت میکند.
working tree میز کار نامرتب شماست. شما اینجا فایلها را ویرایش میکنید، پوشهها را حذف میکنید و آزمایش میکنید. هنوز هیچ چیز ایمن نیست. staging area یا همان index، جایی است که شما بهصورت انتخابی تعیین میکنید چه چیزی در اسنپشات (snapshot) بعدی قرار بگیرد. اجرای git add روی یک فایل، آن را از working tree به staging منتقل میکند. این به شما دقت میدهد. میتوانید ده فایل را تغییر دهید، فقط سه تای آنها را stage کنید و یک اسنپشات تمیز و منطقی را commit کنید که واقعاً توصیفکننده یک تغییر است. وقتی دستور git commit را اجرا میکنید، local repository اسنپشات را دریافت میکند. در آن نقطه، Git وضعیت کامل فایلهای stage شده را به همراه پیام شما ثبت میکند و یک نقطه بازگشت (checkpoint) دائمی ایجاد میکند که میتوانید بعداً به آن بازگردید.
قبل از اینکه چیزی را stage کنید، git status را اجرا کنید. این دستور فایلهای ردیابینشده (untracked) و فایلهای تغییریافتهای را که ممکن است فراموش کرده باشید، به شما نشان میدهد. اگر این بررسی را انجام ندهید، آرتیفکتهای ساخت موقت، فایلهای لاگ یا فایلهای محیطی (environment files) ممکن است وارد commitها شوند. یک فایل .gitignore خوب کمک میکند، اما git status بازرسی نهایی شما پیش از پرواز است.
مرحله staging همچنین به شما اجازه میدهد اشتباهات را قبل از اینکه به تاریخ تبدیل شوند، اصلاح کنید. اگر فایلی را زودتر از موعد اضافه کردید، با git restore --staged آن را از حالت stage خارج کنید. اگر پیام commit شما خیلی مبهم بود، آن را بازنویسی کنید. staging area دقیقاً به این دلیل وجود دارد که commitهای شما یک داستان منسجم را روایت کنند، نه اینکه فقط یک تخلیه خام از تمام کلیدهایی باشد که از زمان ناهار زدهاید.
جمعبندی
این دو حوزه، یعنی مرورگر و Git، تقریباً هر ساعت از گردش کار شما را شکل میدهند. در مرورگر، باید درک کنید که درخواستها چگونه حل میشوند، DOM چگونه به اسکریپتهای شما واکنش نشان میدهد و دادهها کجا در سمت کلاینت قرار دارند. استفاده نادرست از LocalStorage برای ذخیره اطلاعات حساس یا فشار آوردن به DOM با بهروزرسانیهای غیردستهبندیشده (unbatched)، اپلیکیشنهای شکننده و کندی ایجاد میکند. در ترمینال، برخورد با Git مانند یک دکمه ذخیره (save button)، تاریخی ایجاد میکند که هیچکس، از جمله خودِ آیندهتان، نمیتواند آن را بخواند. از staging area آگاهانه استفاده کنید. وضعیت خود را بررسی کنید. commitهایی بنویسید که توضیح دهند «چرا»، نه فقط «چه چیزی».
عادتی که هر دو جهان را به هم پیوند میدهد، «بازرسی» است. قبل از اینکه API را مقصر بدانید، URLها را بررسی کنید. قبل از اضافه کردن یک فریمورک، عملکرد DOM را تحلیل (profile) کنید. قبل از خرید یک سرور بزرگتر، تب Network را بخوانید. قبل از اینکه یک اشتباه را نهایی کنید، git status را مرور کنید. ابزارها از قبل روی صفحه شما باز هستند. یادگیریِ خواندن صادقانه آنها، وظیفه اصلی شماست.
