منابع آموزشی و پژوهشی، برای دانشگاه و مدرسه
پشتیبانیانتشار محتوا
مسیر یادگیری

جاوااسکریپت ۲؛ دورهٔ متوسط و پروژه‌های کاربردی

دورهٔ دوم و رایگان جاوااسکریپت، تألیف تحریریه درس‌آوند؛ ادامهٔ دورهٔ «جاوااسکریپت از صفر تا پروژه». ۲۴ درس سطح متوسط با مثال عملی، تمرین و پاسخ و سه پروژهٔ مدیریت هزینه، فروشگاه و API محلی تیکت. پیش‌نیاز: تسلط بر مبانی دورهٔ اول و HTML مقدماتی. زمان هر درس برآورد مطالعه و تمرین است؛ این نسخه ویدیو ندارد.

۲۴ درس۱٬۱۰۰ دقیقه۰ درس کامل‌شده
پرسش دربارهٔ این دوره
قرارداد داده و طراحی کد · درس ۲

۲. مرجع و تغییر داده؛ چرا ویرایش یک ردیف جای دیگری را عوض می‌کند؟

دورهٔ رایگانثبت‌نام و پیشرفت یادگیری در حساب شما نگهداری می‌شود.ورود / ساخت حساب

در برنامهٔ دفتر هزینه، کاربر یک ردیف را برای ویرایش باز می‌کند و سپس «انصراف» را می‌زند. انتظار طبیعی این است که فهرست اصلی تغییر نکند. اگر فرم و فهرست به همان شیء اشاره کنند، تایپ در فرم ممکن است پیش از ذخیره، دادهٔ اصلی را تغییر دهد. برای برنامه‌نویس سطح متوسط، شناخت این رفتار از حفظ‌کردن نام روش‌های کپی مهم‌تر است.

در این درس، کدها را مستقل در Node.js ۲۴ یا کنسول مرورگر اجرا کنید. داده‌ها کوچک‌اند تا تغییرها قابل مشاهده باشد. در پروژهٔ واقعی، ابتدا مشخص کنید کدام بخش مالک داده است و تغییر در چه نقطه‌ای قطعی می‌شود؛ سپس روش کپی را انتخاب کنید.

کپی آرایه، کپی ردیف نیست

javascript
const original = [{ id: 'e1', amount: 100000, meta: { note: 'اولیه' } }];
const next = [...original];
next[0].amount = 120000;
console.log(original[0].amount); // 120000
console.log(next === original); // false
console.log(next[0] === original[0]); // true

آرایهٔ بیرونی تازه ساخته شده، اما هر دو خانه هنوز به یک شیء اشاره می‌کنند. نوشتن const هم شیء را ثابت نمی‌کند؛ فقط اجازه نمی‌دهد همان نام را به مقدار دیگری نسبت دهید. اگر همین آزمایش را با Object.freeze(original) انجام دهید، فقط خود آرایه منجمد می‌شود و شیءهای درون آن لزوماً منجمد نیستند. انجماد سطحی را با حفاظت کامل اشتباه نگیرید.

اکنون همان عملیات را با جایگزینی ردیف انجام می‌دهیم. به‌جای تغییر مستقیم، فقط ردیف هدف را از نو می‌سازیم:

javascript
const expenses = [{ id: 'e1', amount: 100000 }, { id: 'e2', amount: 80000 }];
const updated = expenses.map(item =>
  item.id === 'e1' ? { ...item, amount: 120000 } : item
);
console.log(expenses[0].amount, updated[0].amount); // 100000 120000
console.log(updated[0] === expenses[0]); // false
console.log(updated[1] === expenses[1]); // true

اشتراک ردیف دوم در اینجا عمدی است؛ چون آن را تغییر نمی‌دهیم. مزیت این روش روشن‌بودن محل تغییر است. لازم نیست برای هر تغییر کوچک، کل دادهٔ برنامه را عمیق کپی کنیم. در فایل projects/expenses/model.mjs نیز تابع upsert با ساختن آرایهٔ نتیجه کار می‌کند و اعتبارسنجی را پیش از پذیرش ردیف انجام می‌دهد.

دادهٔ تو در تو را آگاهانه کپی کنید

گسترش شیء هم سطحی است. اگر ردیف دارای meta باشد، برای تغییر meta.note باید همان سطح را نیز جایگزین کنید:

javascript
const row = { id: 'e1', meta: { note: 'اولیه', source: 'manual' } };
const draft = { ...row, meta: { ...row.meta, note: 'نسخهٔ فرم' } };
console.log(row.meta.note); // اولیه
console.log(draft.meta.note); // نسخهٔ فرم

این الگو برای ساختارهای کوچک و مشخص مناسب است، چون از روی کد می‌توان مسیر تغییر را دید. اگر ساختار بسیار عمیق شده، پیش از افزودن چندین گسترش تو در تو، طراحی داده را بازبینی کنید. مثلاً نگهداری شناسهٔ محصول در سبد به‌جای یک کپی بزرگ از محصول، هم ابهام قیمت را کم می‌کند و هم ویرایش را ساده‌تر می‌سازد.

structuredClone برای پیش‌نویس مستقل

وقتی واقعاً به نسخه‌ای مستقل از دادهٔ سازگار نیاز دارید، structuredClone انتخاب قابل بررسی است. این مثال مستقل را اجرا کنید:

javascript
const source = {
  title: 'گزارش',
  settings: { currency: 'TOMAN' },
  generatedAt: new Date('2026-01-01T00:00:00Z')
};
const copy = structuredClone(source);
copy.settings.currency = 'IRR';
console.log(source.settings.currency); // TOMAN
console.log(copy.generatedAt instanceof Date); // true

این ابزار راه عمومی کپی هر چیز در برنامه نیست. تابع و عنصر DOM از جمله داده‌هایی نیستند که بتوان به این شکل کپی کرد. کپی یک نمونهٔ کلاس نیز جای قرارداد صریح ساخت مدل را نمی‌گیرد. وقتی داده از فایل یا شبکه می‌آید، مستقل‌بودن کپی، معتبر‌بودن آن را تضمین نمی‌کند؛ هنوز باید فیلدها و محدودیت‌ها بررسی شوند. کپی عمیق و اعتبارسنجی دو هدف جدا دارند.

تبدیل رفت‌وبرگشتی با JSON.stringify و JSON.parse را هم کپی بی‌نقص همهٔ انواع ندانید. در مثال بالا، تاریخ پس از JSON به متن تبدیل می‌شود. اگر قرارداد پشتیبان شما فقط متن، عدد، آرایه و شیء ساده دارد، JSON برای انتقال مناسب است؛ انتخاب آن باید بر اساس قرارداد باشد، نه عادت.

یک راه ساده برای یافتن تغییر ناخواسته این است که پیش از اجرای عملیات، خروجی مورد انتظار و مرجع‌های مشترک را روی کاغذ مشخص کنید. اگر ردیف‌ها شامل دادهٔ حساس باشند، نسخهٔ پیش‌نویس را بی‌دلیل در کنسول چاپ نکنید. در تمرین فعلی از دادهٔ ساختگی استفاده می‌کنیم و می‌توانیم قبل و بعد عملیات، مقدارها را با هم مقایسه کنیم. این بازبینی به‌ویژه هنگام افزودن دکمهٔ «انصراف» مفید است، چون ذخیرهٔ موفق به‌تنهایی این خطا را نشان نمی‌دهد.

تمرین و پاسخ تشریحی

یک تابع editNote(rows, id, note) بنویسید که آرایهٔ تازه برگرداند، یادداشت ردیف هدف را تغییر دهد و آرایه و meta قبلی را دست‌نخورده نگه دارد. اگر شناسه پیدا نشد، سیاست روشنی داشته باشید؛ در این تمرین همان آرایهٔ ورودی را برگردانید و این رفتار را در آزمون ثبت کنید.

پاسخ راهنما: با some وجود شناسه را بررسی کنید، سپس map بزنید و فقط برای ردیف هدف، هم شیء ردیف و هم meta را گسترش دهید. یادداشت باید متن باشد و محدودیت طول داشته باشد. یک آزمون مقدار، یک آزمون هویت مرجع و یک آزمون انصراف از فرم بنویسید. معیار تحویل فقط نمایش متن تازه نیست؛ پیش‌نویس باید بدون اثر جانبی کنار گذاشته شود. در بازبینی کد توضیح دهید چرا کپی کامل همهٔ ردیف‌ها برای همین مسئله لازم نبود.

منابع رسمی برای مطالعهٔ تکمیلی

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

تألیف و مثال‌های اختصاصی: تحریریه درس‌آوند.

یکجا انتخاب کن، راحت‌تر دریافت کن

انتخاب‌های من ۰ منبع

مجموعهٔ مطالعه‌ات را بساز.

کنار مقاله‌ها «انتخاب» را بزنید تا چند فایل را با هم دریافت کنید.

انتخاب مقاله‌های رایگان