ترفندهای بهینه سازی کد جاوا اسکریپت برای توسعه دهندگان فرانت اند | راهنمای کاربردی
جاوا اسکریپت امروزه به یکی از محبوبترین زبانهای برنامهنویسی در طول تاریخ برنامهنویسی تبدیل شده است. بر اساس گزارش W3Tech این زبان به وسیله 96% از وبسایتها در سراسر دنیا مورد استفاده قرار میگیرد. یک واقعیت کلی که باید در مورد وب در نظر داشته باشیم، این است که ما هیچ کنترلی روی خصوصیات سختافزار دستگاههای کاربرانی که به وبسایتها دسترسی مییابند، نداریم. کاربر نهایی ممکن است روی یک دستگاه بهروز و پیشرفته از وبسایت بازدید کند و یا از یک دستگاه قدیمی و با مشخصات پایین این کار را انجام دهد. همچنین سرعت اتصال میتواند کاملاً متفاوت باشد. همه این شرایط به آن معنی است که باید کاری کنید که وبسایت تا حد امکان بهینه باشد تا بتوانید نیازمندیهای همه کاربران را رفع کنید. در این مقاله 14 ترفند بهینه سازی کد جاوا اسکریپت معرفی میکنیم که به شما به عنوان یک توسعهدهنده فرانتاند کمک میکنند تا بتوانید این الزامات را برآورده سازید.
حذف قابلیتها و کدهای بیاستفاده
هر چه اپلیکیشن شما شامل کد بیشتری باشد، به پهنای دادهی بیشتری برای انتقال آن به کلاینت نیاز دارد. همچنین مرورگر برای تحلیل و تفسیر کد به زمان بیشتری نیاز دارد. ممکن است برخی قابلیتها را در اپلیکیشن خود گنجانده باشید که هرگز استفاده نخواهند شد. بهتر است این کدهای اضافی را تنها در محیط توسعه حفظ کنید و آنها را وارد کد پروداکشن نکنید تا باری اضافی بر مرورگر کلاینت از جهت کدهای بیاستفاده تحمیل نشود.
همواره از خود بپرسید آیا وجود این تابع، قابلیت یا این بخش از کد واقعاً ضرورت دارد؟
امکان حذف کدهای بیاستفاده به صورت دستی یا با استفاده از ابزارهایی مانند Uglify یا Closure Compiler گوگل ممکن است. حتی میتوانید از یک تکنیک به نام «درختتکانی» (Tree Shaking) استفاده کنید که کدهای بیاستفاده را از اپلیکیشن حذف میکند. برخی Bundler-ها مانند Webpack این تکنیک را ارائه میکند. برای کسب اطلاعات بیشتر در خصوص تکنیک «درختتکانی» و تکنیکهای بهینهسازی کدهای جاوا اسکریپت میتوانید از این مقاله فرادرس استفاده کنید. اگر میخواهید پکیجهای بیاستفاده npm را حذف کنید، میتوانید از دستور npm prune استفاده کنید. برای کسب اطلاعات بیشتر به مستندات NPM مراجعه کنید.
هر زمان که ممکن است از cache استفاده کنید
فرایند «کَش کردن» (Caching) موجب افزایش سرعت و عملکرد وبسایت از طریق کاهش تأخیر و ترافیک شبکه میشود و از این رو زمان مورد نیاز برای نمایش یک بازنمایی از یک منبع را کاهش میدهد. این وضعیت با بهرهگیری از Cache API یا HTTP caching قابل حصول است. شاید کنجکاو باشید که در زمان تغییر یافتن محتوا چه اتفاقی میافتد. مکانیسمهای caching که اشاره کردیم، از توانایی مدیریت و بازتولید کش در زمانی که شرایط خاصی مانند انتشار محتوای جدید وجود داشته باشد، برخوردار هستند.
جلوگیری از نشت حافظه
با در نظر گرفتن این واقعیت که جاوا اسکریپت یک زبان سطح بالا است، نیازمند چند روش مدیریت سطح پایین از قبیل مدیریت حافظه است. برای نمونه فرایند Garbage Collection یک پردازش رایج در بسیاری از زبانهای برنامهنویسی برای مدیریت حافظه است. منظور از Garbage Collection به بیان غیر فنی، گردآوری اشیایی است که هم اینک استفاده نمیشوند تا حافظه اختصاص یافته به آنها در برنامه آزاد شود. در زبانهای برنامهنویسی مانند C، توسعهدهنده خود وظیفه تخصیص و آزادسازی حافظه را با استفاده از تابعهای ()malloc و ()dealloc بر عهده دارد.
با این که پردازش Garbage Collection در جاوا اسکریپت به صورت خودکار اجرا میشود، اما برخی موارد خاص وجود دارند که نیازمند دخالت دستی ما هستند. در جاوا اسکریپت ES6 دو مورد Map و Set با همنیاهای ضعیفترشان معرفی شدهاند. این همتایان ضعیفتر به نامهای WeakMap و WeakSet شناخته میشوند و یک ارجاع «ضعیف» به اشیا نگه میدارند. بدین ترتیب امکان گردآوری و آزادسازی مقادیر ارجاعزداییشده و جلوگیری از نشت حافظه را فراهم میسازند.
در اولین فرصت از حلقهها خارج شوید
تعریف حلقه برای چرخههای بزرگ، قطعاً موجب مصرف شدن زمان ارزشمند زیادی میشود. به همین دلیل است که باید همواره تلاش کنیم در اولین فرصت ممکن از حلقهها خارج شویم. این کار به کمک کلیدواژه break و کلیدواژه continue میسر است. وظیفه شما این است که همواره کارآمدترین کد ممکن را بنویسید.
در مثال زیر، اگر از حلقه خارج نشوید (break نکنید)، کد شما 1000000000 بار اجرا میشود که بدیهی است یک بار اضافی محسوب میشود:
1let arr = new Array(1000000000).fill('----');
2arr[970] = 'found';
3for (let i = 0; i < arr.length; i++) {
4 if (arr[i] === 'found') {
5 console.log("Found");
6 break;
7 }
8}
در مثال زیر، اگر از continue در مواردی که حلقه با شرط شما تطبیق نمییابد، استفاده نکنید، تابع به مقدار 1000000000 بار اجرا میشود. ما تنها عنصر آرایه را در مواردی پردازش میکنیم که در جایگاه زوج باشد. این امر موجب کاهش زمان اجرای حلقه به میزان تقریباً نصف میشود.
1let arr = new Array(1000000000).fill('----');
2arr[970] = 'found';
3for (let i = 0; i < arr.length; i++) {
4 if(i%2!=0){
5 continue;
6 };
7 process(arr[i]);
8}
کاهش تعداد دفعات محاسبه متغیرها
برای کاهش تعداد دفعاتی که یک متغیر محاسبه میشود، میتوان از «کلوژرها» (closures) استفاده کرد. به بیان ساده کلوژر در جاوا اسکریپت موجب میشود بتوانیم از یک تابع با دامنه درونی به تابعی با دامنه بیرونیتر دسترسی پیدا کنیم. کلوژرها هر بار که تابع ایجاد میشود و نه زمانی که فراخوانی شود، ایجاد میشوند. تابعهای درونی به متغیرهای دامنه بیرونی حتی پس از این که تابع بیرونی بازگشت یافت، دسترسی خواهند داشت. در ادامه دو مثال از کلوژر را بررسی میکنیم تا به صورت عملی با این مفهوم آشنا شویم.
1function findCustomerCity(name) {
2 const texasCustomers = ['John', 'Ludwig', 'Kate'];
3 const californiaCustomers = ['Wade', 'Lucie','Kylie'];
4
5 return texasCustomers.includes(name) ? 'Texas' :
6 californiaCustomers.includes(name) ? 'California' : 'Unknown';
7};
اگر تابعهای فوق را چند بار فراخوانی کنیم، هر بار یک شیء جدید ایجاد میشود. برای هر بار فراخوانی، حافظه به طرزی غیر ضروری مجدداً به متغیرهای texasCustometrs و californiaCustomers تخصیص مییابد.
با استفاده از یک راهکار مبتنی بر کلوژر میتوانیم متغیرها را تنها یک بار وهلهسازی کنیم. به مثال زیر توجه کنید.
1function findCustomerCity() {
2 const texasCustomers = ['John', 'Ludwig', 'Kate'];
3 const californiaCustomers = ['Wade', 'Lucie','Kylie'];
4
5 return name => texasCustomers.includes(name) ? 'Texas' :
6 californiaCustomers.includes(name) ? 'California' : 'Unknown';
7};
8let cityOfCustomer = findCustomerCity();
9cityOfCustomer('John');//Texas
10cityOfCustomer('Wade');//California
11cityOfCustomer('Max');//Unknown
در این مثال، به کمک کلوژرها، تابع درونی که به متغیر cityOfCustomer بازگشت مییابد به محتوای تابع بیرونی ()findCustomerCity دسترسی مییابد. بدین ترتیب هر زمان که تابع درونی با نامی که به عنوان یک پارامتر ارسال شده، فراخوانی شود، نیازی به وهلهسازی مجدد از ثابتها وجود نخواهد داشت.
کاهش دسترسی به DOM
دسترسی به DOM در قیاس با گزارههای جاوا اسکریپت کُند است. اگر تغییری در DOM ایجاد کنید که منجر به بازسازی مجدد لیآوت شود، منجر به کند شدن همه چیز خواهید شد. برای کاهش دفعات دسترسی به یک عنصر DOM، میتوانید یک بار به آن دسترسی پیدا کنید و سپس از آن به عنوان یک متغیر لوکال استفاده کنید. زمانی که نیاز شما رفع شد، مطمئن شوید که مقدار متغیر را با تعیین مقدار null حذف میکنید. به این ترتیب از نشت حافظه جلوگیری میکنید و اجازه میدهید که فرایند garbage collection وظیفه خود را اجرا کند.
فشردهسازی فایلها
با استفاده از روشهای فشردهسازی از قبیل Gzip، میتوان اندازه فایلهای جاوا اسکریپت را کاهش داد. هر چه فایلها کوچکتر باشند، عملکرد وبسایت افزایش مییابد، زیرا مرورگر باید دادههای کمتری را دانلود کند. این فشردهسازیها میتوانند اندازه فایل را تا میزان 80% کاهش دهند. برای کسب اطلاعات بیشتر به این صفحه (+) مراجعه کنید.
Minify کردن کد نهایی
برخی افراد بر این باورند که Minify کردن و فشردهسازی کد، فرایندهای یکسانی هستند. اما واقعیت این است که این دو کاملاً متفاوت هستند. در زمان فشردهسازی، برخی الگوریتمهای خاص برای تغییر اندازه خروجی فایل مورد استفاده قرار میگیرند. در زمان Minify کردن، کامنتها و فواصل اضافی از فایلهای جاوا اسکریپت حذف میشود. این فرایند به کمک ابزارها و پکیجهای مختلفی اجرا میشود.
فرایند Minify کردن یک رویه استاندارد برای بهینهسازی صفحه محسوب میشد و نقشی عمده در بهینهسازی فرانتاند دارد. Minify کردن میتواند حجم فایلهای شما را تا 60% کاهش دهد. برای کسب اطلاعات بیشتر در این خصوص به این صفحه (+) مراجعه کنید.
از Throttle و Debounce استفاده کنید
با بهرهگیری از این دو تکنیک، میتوانیم تعداد دفعاتی که یک رویداد باید از سوی کد مدیریت شود را به صورت دقیقی کنترل کنیم. Throttle کردن به معنی تعیین تعداد بیشینه دفعاتی است که یک تابع میتواند در طی دوره زمانی خاص فراخوانی شود. برای نمونه میتوانیم اعلام کنیم که تابع رویداد onkeyup دست بالا هر 1000 میلیثانیه یک بار اجرا شود. این بدان معنی است که اگر با سرعت 20 کلید بر ثانیه تایپ کنید، این رویداد تنها یک بار در هر ثانیه اجرا میشود. به این ترتیب بار کاری روی کد کاهش مییابد.
از سوی دیگر Debounce کردن به معنی تعیین کمینه زمانی برای اجرای یک تابع پس از اجرای قبلی است. به بیان دیگر ما در Debounce اعلام میکنیم که «این تابع را تنها در صورتی که 600 میلیثانیه از فراخوانی قبلی سپری شده باشد، اجرا کن». این بدان معنی است که تابع شما نمیتواند تا زمانی که 600 میلیثانیه از فراخوانی قبلی سپری نشده است، اجرا شود. برای کسب اطلاعات بیشتر در این خصوص به این مقاله (+) مراجعه کنید. شما میتوانید هر یک از کارکردهای debounce و throttle را در پروژه خود پیادهسازی کنید و یا آنها را از کتابخانههایی مانند Lodash یا Underscore ایمپورت کنید.
از کلیدواژه delete استفاده نکنید
کلیدواژه delete برای حذف مشخصه از یک شیء استفاده میشود. اما در خصوص عملکرد این کلیدواژه تردیدهایی وجود دارد. این مشکل احتمالاً در آینده حل خواهد شد. به عنوان یک رویکرد جایگزین، میتوانید مشخصههایی را که نیاز ندارید، به صورت undefined مقداردهی کنید.
1const object = {name:"Jane Doe", age:43};
2object.age = undefined;
همچنین میتوانید از شیء Map استفاده کنید، چون متد delete آن سریعتر است.
استفاده از کدهای ناهمگام برای جلوگیری از انسداد thread
همچنان که میدانید جاوا اسکریپت به صورت پیشفرض یک زبان همگام و همچنین «تکنخی» (single-threaded) است. اما ممکن است مواردی باشند که کد شما برای محاسبه نیاز به زمان زیادی داشته باشد. داشتن ماهیت همگام به این معنی است که این قطعه کد ممکن است موجب انسداد اجرای گزارههای دیگر شود تا این که اجرایش پایان گیرد. این کار موجب کاهش عملکرد کلی کد میشود.
از طریق پیادهسازی کد ناهمگام میتوانیم از بروز این شرایط جلوگیری کنیم. کد ناهمگام قبلاً به شکل callback نوشته میشد، اما اینک یک روش جدید برای مدیریت کد ناهمگام در ES6 معرفی شده است. این سبک جدید به نام Promise شناخته میشود. برای کسب اطلاعات بیشتر در خصوص Promise-ها میتوانید از مستندات رسمی MDN (+) و یا از مقالات زیر استفاده کنید:
- Promise در جاوا اسکریپت و کاربردهای آن — به زبان ساده
- برنامهنویسی ناهمگام جاوا اسکریپت با Promise ها — راهنمای کاربردی
- Promise.all در جاوا اسکریپت — از صفر تا صد
اما شاید بپرسید حال که جاوا اسکریپت ماهیتی همگام و تکنخی دارد، چطور میتوانیم کد ناهمگام را در آن اجرا کنیم؟ این موضوع موجب سردرگمی افراد زیادی میشود. این کار به لطف موتور جاوا اسکریپت که در پسزمینه در مرورگر اجرا میشود، میسر شده است. منظور از موتور جاوا اسکریپت، یک برنامه رایانهای است که کد جاوا اسکریپت را تفسیر کرده و اجرا میکند. موتور جاوا اسکریپت به وسیله طیف مختلفی از زبانهای برنامهنویسی نوشته میشود. برای نمونه موتور V8 که برای مرورگر گوگل کروم ارائه شده است به وسیله ++C نوشته شده است، اما موتور SpiderMonkey که نیروی مرورگر فایرفاکس را تأمین میکند به وسیله زبانهای C و C++ نوشته شده است.
این موتورهای جاوا اسکریپت میتوانند وظایف مختلفی را در پسزمینه مدیریت کنند. «پشته فراخوانی» (callstack) تابعهای Web API را تشخیص داده و آنها را برای مدیریت شدن در اختیار مرورگر قرار میدهد. زمانی که این وظایف از سوی مرورگر پایان یافت، بازگشت مییابند و به صورت یک callback به پشته وارد میشوند. ممکن است گاهی اوقات تعجب کنید که این کار در Node.js چطور انجام میشود، زیرا هیچ مرورگری ندارد که این فرایند را اجرا کند. در واقع همان موتور V8 مرورگر گوگل کروم برای Node.js نیز استفاده میشود.
از افراز کد استفاده کنید
اگر با Google Light House آشنا باشید، با مفهومی به نام «نخستین نمایش محتوا» (First Contentful Paint) نیز آشنا هستید. این مفهوم یکی از شش معیاری است که در بخش عملکرد گزارش Lighthouse ردگیری میشود. «نخستین نمایش محتوا» یا به اختصار FCP مدت زمانی را که طول میکشد تا مرورگر نخستین بخش از محتوای DOM را پس از ورود کاربر به یک صفحه رندر کند، اندازهگیری میکند. تصاویر، عناصر غیر سفید بوم (<canvas>) و SVG-ها در صفحه به عنوان محتوای DOM در نظر گرفته میشوند، اما هر چیزی که درون یک iframe باشد، در این فهرست قرار نمیگیرد.
یکی از بهترین روشها برای دستیابی به نمرات بالاتر FCP استفاده از «افراز کد» (code splitting) است. افراز کد یک تکنیک است که در آن ابتدا ماژولهایی که در ابتدا برای کاربر لازم است، ارسال میشوند. به این ترتیب با کاهش اندازه حجم کد ارسالی در ابتدا، نمره FCP بهبود مییابد. Bundler-های رایج ماژول ماند webpack کارکرد افراز کد را در اختیار شما قرار میدهند. همچنین میتوانید از ماژولهای نیتیو ES استفاده کنید که موجب میشوند ماژولها به صورت منفرد بارگذاری شوند.
از async و defer استفاده کنید
در وبسایتهای مدرن، اسکریپتها بسیار حجیمتر از HTML هستند و اندازه آنها بزرگتر بوده و به زمان پردازشی بیشتری نیاز دارند. مرورگر به صورت پیشفرض باید صبر کند تا اسکریپت دانلود شود تا بتواند آن را اجرا کد و سپس بقیه صفحه را پردازش کند. این وضعیت میتواند منجر به انسداد اسکریپتی در بارگذاری صفحه شود. برای رفع این مشکل جاوا اسکریپت دو تکنیک به نامهای async و defer در اختیار ما قرار داده است. شما باید این خصوصیتها را به تگهای <script> اضافه کنید.
Async در جایی استفاده میشود که باید به مرورگر اعلام کنیم اسکریپت را بدون تأثیرگذاری روی رندرینگ صفحه بارگذاری کند. به بیان دیگر، صفحه منتظر اسکریپتهای Async نمیماند و محتوای آن به صورت همزمان از سوی مرورگر پردازش و تحلیل میشود. Defer به مرورگر اعلام میکند که باید اسکریپت را پیش از کامل شدن رندرینگ صفحه بارگذاری کند. اگر هر دو تگ اعلام شده باشند، Async در مرورگرهای مدرن تقدّم دارد؛ در حالی که در مرورگرهای قدیمیتر که از defer پشتیبانی میکنند، Async به عنوان fallback برای defer استفاده میشود. این دو خصوصیت میتوانند کمک زیادی به کاهش زمان بارگذاری صفحه بکنند.
استفاده از وبورکرها برای اجرای وظایف پردازشی سنگین در پسزمینه
وبورکرها (Web Workers) به ما امکان میدهند که اسکریپتها را در نخهای پسزمینه اجرا کنیم. اگر برخی وظایف خاص دارید که از نظر پردازشی سنگین هستند، میتوانید اجرای آنها را به وبورکرها محول کنید تا بدون تداخل با رابط کاربری اجرا شوند. وبورکر پس از ایجاد شدن، میتواند با ارسال پیامهایی به یک «دستگیره رویداد» (event handler) که از سوی کد تعیین شده است، با کد جاوا اسکریپت ارتباط بگیرد. البته این فرایند به صورت معکوس نیز قابل اجرا است. برای کسب اطلاعات بیشتر در خصوص وبورکرها به مستندات رسمی MDN (+) مراجعه کنید.
سخن پایانی
به این ترتیب به پایان این مقاله با موضوع معرفی 14 ترفند کاربردی جاوا اسکریپت میرسیم.
امیدواریم این ترفندها به شما کمک کنند تا بتوانید کدی منسجمتر و سریعتر بنویسید. به عنوان آخرین نکته، یادآوری میکنیم که بهتر است از Bit (+) برای اشتراک، مستندسازی و مدیریت کامپوننتهای با استفاده مجدد در پروژههای مختلف استفاده کنید.
Bit یک روش عالی برای افزایش قابلیت استفاده مجدد از کد، افزایش سرعت توسعه و ساخت اپلیکیشنهای مقیاسپذیر محسوب میشود.