در این درس میخوانید
در پروژهٔ کوچک هم میتوان از روز اول عادتهای درست داشت: ورودی را داده بدانیم، مجوز را در جای درست بررسی کنیم و کار طولانی را بدون اندازهگیری به صفحه تحمیل نکنیم. درس نوزدهم سه موضوع مرتبط را کنار هم میگذارد: امنیت دادهٔ ورودی، پاسخگویی رابط و دسترسی با صفحهکلید. هدف این نیست که با یک ترفند همهٔ خطرها یا همهٔ کندیها برطرف شوند؛ میخواهیم تصمیمهای پایه قابل توضیح و آزمون باشند.
XSS چگونه به رفتار برنامه مربوط میشود؟
وقتی دادهٔ کنترلنشده به محل تفسیر HTML یا کد برسد، ممکن است کد ناخواسته با دسترسی همان سایت اجرا شود. برای نام کاربر، عنوان درس و پیام ساده نیازی به HTML نداریم؛ از textContent و ساخت Node استفاده میکنیم. اگر نمایش HTML غنی واقعاً لازم است، پاکسازی معتبر باید با زمینهٔ مصرف و سیاست برنامه سازگار باشد. جایگزینی چند علامت با یک regex دلخواه، یک راهحل عمومی XSS نیست.
const titleFromInput = "<b>عنوان واردشده</b>";
const heading = document.createElement("h2");
heading.textContent = titleFromInput;
document.body.append(heading);
// علامتهای <b> بهصورت متن دیده میشوند، نه یک تگ اجراشده.این مثال به محیط مرورگر، پروژه یا Node.js نیاز دارد؛ طبق راهنمای درس اجرا کنید.URL هم ورودی است
امنبودن متنِ لینک به معنای معتبر بودن مقصد نیست. اگر URL از کاربر میآید، پروتکل و در صورت نیاز میزبان مجاز را بررسی کنید. نمونهٔ زیر فقط HTTPS بدون اطلاعات نام کاربری و رمز در URL را میپذیرد؛ این قاعده بهتنهایی اعتبار محتوای مقصد یا ضدفیشینگبودن آن را تضمین نمیکند. برای مقصدهای حساس، allowlist دامنه و قرارداد مشخص لازم است.
function makeReferenceLink(rawUrl, label) {
const url = new URL(rawUrl);
if (url.protocol !== "https:" || url.username || url.password) {
throw new TypeError("Allowed HTTPS URL required");
}
const link = document.createElement("a");
link.href = url.href;
link.textContent = label;
link.target = "_blank";
link.rel = "noopener noreferrer";
return link;
}
document.body.append(makeReferenceLink("https://developer.mozilla.org/", "مستندات MDN"));این مثال به محیط مرورگر، پروژه یا Node.js نیاز دارد؛ طبق راهنمای درس اجرا کنید.مرورگر مرجع مجوز نیست
کاربر میتواند JavaScript، DOM و localStorage خود را تغییر دهد. پنهانکردن یک دکمه یا نگهداشتن isAdmin در مرورگر، کنترل دسترسی ایجاد نمیکند. دریافت فایل پولی، تغییر نقش و ویرایش دادهٔ دیگران باید در سرور و برای هر درخواست مجاز بودن را بررسی کنند. کلید خصوصی API و رمز سرویس را در کد عمومی یا تنظیمات قابل دریافت نگذارید. دادهٔ لازم برای نمایش را حداقل نگه دارید و log را از اطلاعات حساس پاک کنید.
کندی را اندازه بگیرید
کارایی زمان اجرا با فقط زمان دانلود یک فایل یکسان نیست. در مرورگر، کار طولانی روی main thread میتواند کلیک، تایپ و رندر را عقب بیندازد. در ابزارهای وب، task طولانیتر از 50 میلیثانیه long task نامیده میشود؛ این عدد یک تعریف تشخیصی است، نه بودجهٔ مناسب همهٔ تعاملها. با Performance مرورگر یک سناریوی مشخص را ثبت کنید و روی دستگاه یا شبیهسازی مناسب نیز بررسی کنید. یک عدد روی لپتاپ قوی، تجربهٔ همهٔ کاربران را نمایندگی نمیکند.
const values = Array.from({ length: 1000 }, (_, index) => index);
const started = performance.now();
const sum = values.reduce((total, value) => total + value, 0);
console.log(sum); // 499500
console.log("elapsed ms:", performance.now() - started);این مثال فقط روش مشاهدهٔ زمان را نشان میدهد و بنچمارک معتبر مقایسهٔ ابزارها نیست. تعداد داده، بهینهسازی موتور، وضعیت دستگاه و کارهای دیگر نتیجه را تغییر میدهند. ابتدا گلوگاه واقعی را پیدا کنید؛ تغییر ساختار همهٔ کدها به امید چند میلیثانیه، ممکن است فقط نگهداری را دشوار کند. در رابط، تعداد Nodeها و الگوی خواندن و نوشتن layout نیز میتوانند مهمتر از یک محاسبهٔ کوچک باشند.
کار را به بخشهای قابل مدیریت تقسیم کنید
اگر کار قابل تقسیم است، میتوان بین بخشها به مرورگر فرصت ادامهٔ کار داد. تابع بعدی نمونهای مقدماتی با taskهای تایمر است. اندازهٔ batch بر حسب تعداد عضو است، نه تضمین زمان؛ اگر هر handle سنگین باشد، batch صدتایی همچنان طولانی میشود. برای محاسبهٔ CPU سنگین و مستقل از DOM، Web Worker را بررسی کنید؛ async کردن تابع بهتنهایی آن را به worker منتقل نمیکند.
async function processInChunks(items, handle, batchSize = 100) {
if (!Number.isInteger(batchSize) || batchSize < 1) {
throw new RangeError("Positive batch size required");
}
for (let start = 0; start < items.length; start += batchSize) {
const batch = items.slice(start, start + batchSize);
for (const item of batch) handle(item);
if (start + batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}
processInChunks([1, 2, 3], value => console.log(value), 2)
.catch(console.error);رویدادها، حافظه و درخواستهای تکراری
ثبت دوبارهٔ listener در هر render، نگهداشتن timerهای بلااستفاده و دریافت داده برای هر ضربهٔ کلید میتواند مشکل ایجاد کند. listener را در جای مناسب ثبت و هنگام پایان عمر بخش، آن را پاک کنید. برای جستوجو، debounce تعداد شروع درخواستها را کم میکند، اما پاسخ دیررس درخواست قبلی را خودکار بیاثر نمیکند؛ لغو یا شناسهٔ درخواست لازم است. Promise.all نیز محدودیت همزمانی ایجاد نمیکند. هر ابزار باید برای مشکل واقعی خودش به کار رود.
دسترسپذیری بخشی از رفتار درست است
- برای عمل، button و برای ناوبری، لینک واقعی به کار ببرید.
- ورودی فرم label مرتبط و پیام خطای قابل فهم داشته باشد.
- با Tab و Enter مسیر اصلی را بدون ماوس آزمایش کنید.
- حالت انتظار و نتیجه را با متن روشن و در صورت نیاز aria-live اعلام کنید.
- پس از حذف یا تغییر محتوا، focus را بیدلیل به جای نامعلوم نفرستید.
تمرین عملی
در فرم درس یازدهم، عنوانی شامل علامتهای HTML وارد کنید؛ باید بهصورت متن دیده شود. سپس فقط با صفحهکلید یک درس اضافه و حذف کنید. در تمرین کارایی، تعداد داده را تدریجی افزایش دهید و یک سناریوی ثابت را در Performance ثبت کنید؛ نتیجهٔ پیش و پس از تغییر را در همان شرایط مقایسه کنید. پاسخ موفق یک عدد خاص نیست: ورودی اجرا نمیشود، کار اصلی با صفحهکلید ممکن است و تغییر کارایی با شواهد همان سناریو سنجیده میشود.
منابع انگلیسی
- MDN — Cross-site scripting
- https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS
- OWASP — DOM based XSS Prevention
- https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html
- web.dev — Optimize long tasks
- https://web.dev/articles/optimize-long-tasks
- Chrome DevTools — Analyze runtime performance
- https://developer.chrome.com/docs/devtools/performance
مثالها و سناریوی بازبینی برای این مجموعه نوشته شدهاند. در درس پایانی، این اصول را در برنامهٔ کوچک مدیریت کارها کنار هم میگذاریم.
پیشنیاز و محیط اجرای مثالها
درس 19 از ۲۰؛ پیشنیاز: درسهای قبلی مجموعه. مثالهای DOM و performance در مرورگر اجرا میشوند. این درس اصول مقدماتی را توضیح میدهد و جای بازبینی امنیتی یک سامانهٔ واقعی را نمیگیرد.
پیشرفت روی همین مرورگر ذخیره میشود.