ریاکتجیاس - چرا تغییرناپذیری در جاوااسکریپت اینقدر مهم (یا لازم) است؟


من در حال حاضر روی فریمورکهای React JS و React Native کار میکنم. در نیمه راه با مفهوم تغییرناپذیری یا کتابخانه Immutable-JS برخورد کردم، زمانی که در مورد پیادهسازی Flux و Redux فیسبوک مطالعه میکردم.

سؤال این است که چرا تغییرناپذیری اینقدر مهم است؟ چه اشکالی در تغییر دادن اشیاء وجود دارد؟ آیا این باعث ساده شدن امور نمیشود؟

با ارائه یک مثال، بیایید یک برنامه ساده خواننده اخبار را در نظر بگیریم که صفحه باز شدن آن یک نمای فهرست از عناوین اخبار است.

اگر من مثلاً یک آرایه از اشیاء را با یک مقدار ابتدا تنظیم کنم، نمیتوانم آن را دستکاری کنم. این همان چیزی است که اصل غیرقابل تغییر بودن میگوید، درست است؟ (اگر اشتباه میکنم، اصلاحم کنید.) اما، اگر یک شیء جدید News داشته باشم که باید بهروزرسانی شود چه؟ در حالت معمول، من میتوانستم فقط شیء را به آرایه اضافه کنم. در این حالت چگونه این کار را انجام دهم؟ فروشگاه را حذف و دوباره ایجاد کنم؟ اضافه کردن یک شیء به آرایه عملیات کمهزینهتری نیست؟

پاسخ شماره ۱

من اخیراً در حال تحقیق درباره همان موضوع بودهام. سعی خواهم کرد بهترین پاسخ را به سوال(های) شما بدهم و تلاش میکنم آنچه تاکنون یاد گرفتهام را با شما به اشتراک بگذارم.

سؤال این است که چرا تغییرناپذیری اینقدر مهم است؟ چه اشکالی در تغییر دادن اشیاء وجود دارد؟ آیا این کار باعث سادهتر شدن امور نمیشود؟

اساساً این مسئله به این واقعیت برمیگردد که تغییرناپذیری باعث افزایش پیشبینیپذیری، کارایی (به طور غیرمستقیم) و امکان ردیابی تغییرات میشود.

قابلیت پیشبینی

تغییر پنهان میکند تغییر را، که اثرات جانبی (غیرمنتظره) ایجاد میکند، که میتواند باگهای ناخوشایندی ایجاد کند. وقتی شما غیرقابلتغییر بودن را اعمال میکنید، میتوانید معماری برنامه و مدل ذهنی خود را ساده نگه دارید، که باعث میشود راحتتر بتوانید درباره برنامه خود استدلال کنید.

عملکرد

حتی با وجود این که افزودن مقادیر به یک شیء غیرقابل تغییر به این معنی است که یک نمونه جدید باید ایجاد شود که مقادیر موجود باید کپی شوند و مقادیر جدید باید به شیء جدید اضافه شوند که این هزینه حافظه دارد، اشیاء غیرقابل تغییر میتوانند از اشتراک ساختاری برای کاهش سربار حافظه استفاده کنند.

تمام بهروزرسانیها مقادیر جدیدی بازمیگردانند، اما به طور داخلی ساختارها مشترک هستند تا استفاده از حافظه به طور چشمگیری کاهش یابد (و تداخل جمعآوری زباله). این بدان معناست که اگر به یک بردار با ۱۰۰۰ عنصر اضافه کنید، در واقع بردار جدیدی با ۱۰۰۱ عنصر ایجاد نمیشود. احتمالا، به طور داخلی تنها چند شیء کوچک اختصاص داده میشوند.

شما میتوانید اطلاعات بیشتری در این باره در اینجا بخوانید.

ردیابی جهش

علاوه بر کاهش مصرف حافظه، تغییرناپذیری به شما این امکان را میدهد که با استفاده از برابری مرجع و مقدار، برنامه خود را بهینه کنید. این باعث میشود دیدن اینکه آیا چیزی تغییر کرده است بسیار آسان شود. برای مثال، تغییر وضعیت در یک کامپوننت React. شما میتوانید از shouldComponentUpdate استفاده کنید تا بررسی کنید آیا وضعیت با مقایسه اشیای وضعیت یکسان است و از رندر غیرضروری جلوگیری کنید. میتوانید برای اطلاعات بیشتر اینجا مطالعه کنید.

منابع اضافی:

  • دایو عدم تغییر
  • ساختارهای داده تغییرناپذیر و جاوااسکریپت
  • تغییرناپذیری در جاوااسکریپت

اگر مثلاً یک آرایه از اشیاء را با یک مقدار اولیه تنظیم کنم، نمیتوانم آن را دستکاری کنم. این همان چیزی است که اصل تغییرناپذیری میگوید، درست است؟ (اگر اشتباه میکنم مرا اصلاح کنید). اما، اگر یک شیء News جدید داشته باشم که باید بهروزرسانی شود چه؟ در حالت معمول، میتوانستم به سادگی شیء را به آرایه اضافه کنم. در این حالت چگونه انجامش دهم؟ فروشگاه را حذف کرده و دوباره ایجاد کنم؟ اضافه کردن یک شیء به آرایه عملی کمهزینهتر نیست؟

بله، این درست است. اگر در مورد نحوه پیادهسازی این در برنامه خود سردرگم هستید، توصیه میکنم نگاهی به نحوه انجام این کار توسط Redux بیندازید تا با مفاهیم اصلی آشنا شوید، این به من خیلی کمک کرد.

من دوست دارم از Redux به عنوان مثال استفاده کنم زیرا آن بیتغییری را میپذیرد. این یک درخت حالت واحد و تغییرناپذیر دارد (که به آن store گفته میشود) که در آن تمام تغییرات حالت به صورت صریح با ارسال اکشنها انجام میشود و این اکشنها توسط یک reducer پردازش میشوند که وضعیت قبلی را به همراه آن اکشنها (یکی یکی) میگیرد و حالت بعدی برنامه شما را باز میگرداند. شما میتوانید برای اطلاعات بیشتر در مورد اصول اساسی آن اینجا مطالعه کنید.

یک دوره عالی درباره ریداکس در سایت egghead.io وجود دارد که دن آبراموف، نویسنده ریداکس، این اصول را به شرح زیر توضیح میدهد (من کد را کمی تغییر دادم تا بهتر با سناریو مطابقت داشته باشد):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

همچنین، این ویدیوها جزئیات بیشتری در مورد چگونگی رسیدن به تغییرناپذیری برای موارد زیر نشان میدهند:

  • آرایهها
  • اشیاء

پاسخ شماره ۲

دیدگاهی مخالف نسبت به عدم تغییر

خلاصه: غیرقابل تغییر بودن بیشتر یک روند مد است تا یک ضرورت در جاوااسکریپت. اگر از React استفاده میکنید، این ویژگی راهحلی مرتب برای برخی از انتخابهای طراحی گیجکننده در مدیریت وضعیت ارائه میدهد. با این حال، در بیشتر شرایط دیگر، ارزش کافی نسبت به پیچیدگیای که ایجاد میکند اضافه نمیکند و بیشتر برای پر کردن رزومه است تا برآورده کردن یک نیاز واقعی مشتری.

پاسخ طولانی: در زیر بخوانید.

چرا تغییرناپذیری در جاوااسکریپت اینقدر مهم (یا لازم) است؟

خب، خوشحالم که پرسیدی!

مدتی پیش یک فرد بسیار با استعداد به نام دن ابراموف یک کتابخانه مدیریت وضعیت جاوااسکریپت به نام Redux نوشت که از توابع خالص و عدم تغییرپذیری استفاده میکند. او همچنین چند ویدئوی واقعا جالب ساخت که این ایده را خیلی راحت برای فهمیدن (و تبلیغ کردن) ارائه میکرد.

زمانبندی عالی بود. تازگی Angular در حال کمرنگ شدن بود و دنیای جاوااسکریپت آماده بود تا بر روی آخرین چیزی که درجهی مناسبی از جذابیت داشت تمرکز کند، و این کتابخانه نه تنها نوآورانه بود بلکه بهطور کامل با React که توسط یکی دیگر از قدرتهای سیلیکونولی تبلیغ میشد، مطابقت داشت.

غمانگیز است، اما مدها در دنیای جاوااسکریپت فرمانروایی میکنند. حالا آبراموف به عنوان یک نیمهخدای مورد ستایش قرار گرفته و همه ما انسانهای معمولی باید خودمان را به دِائوی تغییرناپذیری تسلیم کنیم... چه منطقی داشته باشد یا نه.

چه اشکالی در تغییر دادن اشیاء وجود دارد؟

هیچ چیز!

در واقع برنامهنویسان مدتهاست که اشیاء را تغییر دادهاند... همان قدری که اشیاء برای تغییر وجود داشتهاند. به عبارت دیگر، بیش از ۵۰ سال توسعه برنامههای کاربردی.

و چرا چیزها را پیچیده کنیم؟ وقتی یک شیء cat دارید و میمیرد، آیا واقعاً به یک cat دوم برای پیگیری تغییر نیاز دارید؟ اکثر مردم فقط میگویند cat.isDead=true و کارشان تمام میشود.

آیا (تغییر دادن اشیاء) کارها را ساده نمیکند؟

بله! .. البته که دارد!

بهویژه در جاوااسکریپت، که در عمل بیشتر برای رندر کردن نمایی از حالتی که جای دیگری (مانند در یک پایگاه داده) نگهداری میشود، مفید است، استفاده میشود.

اگر یک شیء جدید News داشته باشم که باید بهروزرسانی شود، چه؟ ... در این حالت چگونه این کار را انجام دهم؟ فروشگاه را حذف کرده و دوباره ایجاد کنم؟ اضافه کردن یک شیء به آرایه عملی کمهزینهتر نیست؟

خوب، شما میتوانید از روش سنتی استفاده کنید و شیء News را بهروزرسانی کنید، بنابراین بازنمایی در حافظهی شما از آن شیء تغییر میکند (و نمایی که به کاربر نمایش داده میشود، یا حداقل امیدواریم اینطور باشد)…

یا به طور جایگزین...

شما میتوانید روش جذاب FP/غیرقابل تغییر را امتحان کنید و تغییرات خود را به شیء News به یک آرایه که هر تغییر تاریخی را دنبال میکند اضافه کنید تا بعداً بتوانید در آرایه پیمایش کرده و بفهمید که نمایه صحیح وضعیت باید چه باشد (نفس راحت!).

من در تلاش هستم بفهمم اینجا چه چیزی درست است. لطفاً من را روشن کن :)

مدها میآیند و میروند رفیق. راههای زیادی برای پوست کندن گربه وجود دارد.

متأسفم که مجبور هستید سردرگمی یک مجموعه دائماً در حال تغییر از پارادایمهای برنامهنویسی را تحمل کنید. اما هی، به باشگاه خوش آمدید!!

حالا چند نکته مهم برای به خاطر سپردن در مورد تغییرناپذیری، و اینها با شدت تبآلودی که فقط نادانی میتواند ایجاد کند، به سمت شما پرتاب خواهد شد.

1) تغییرناپذیری برای جلوگیری از شرایط رقابتی در محیطهای چندریسمانی فوقالعاده است.

محیطهای چندنخی (مثل C++، جاوا و C#) مرتکب عمل قفلگذاری روی اشیاء میشوند زمانی که بیش از یک نخ بخواهد آنها را تغییر دهد. این برای عملکرد بد است، اما بهتر از جایگزین آن یعنی خراب شدن دادهها است. و با این حال، به خوبی ساختن همه چیز غیرقابل تغییر نیست (خداوند از هسکل ستایش کند!).

اما افسوس! در جاوااسکریپت شما همیشه روی یک رشته واحد عمل میکنید. حتی وبورکرها (هر کدام در یک زمینه جداگانه اجرا میشوند). بنابراین از آنجا که شما نمیتوانید یک شرایط مسابقه مرتبط با رشته داخل زمینه اجرای خود داشته باشید (تمام آن متغیرهای سراسری و کلوزرهای دوستداشتنی)، نکته اصلی به نفع تغییرناپذیری از بین میرود.

با این حال، یک مزیت استفاده از توابع خالص در وبورکرها وجود دارد، که آن این است که هیچ انتظاری از دستکاری با اشیاء در نخ اصلی نخواهید داشت.

۲) تغییرناپذیری میتواند (به نوعی) از شرایط رقابتی در وضعیت برنامه شما جلوگیری کند.

و اینجا نکته واقعی مسئله است، اکثر توسعهدهندگان (React) به شما خواهند گفت که غیرقابل تغییر بودن و برنامهنویسی تابعی میتوانند به نوعی این جادو را انجام دهند که باعث میشود وضعیت برنامه شما قابل پیشبینی باشد.

البته این به این معنی نیست که شما میتوانید از شرایط مسابقه در پایگاه داده جلوگیری کنید، برای انجام این کار شما باید تمام کاربران در تمام مرورگرها را هماهنگ کنید، و برای آن به یک فناوری پشتیبانی سمت سرور مانند WebSockets نیاز دارید (در ادامه بیشتر در این مورد توضیح داده خواهد شد) که تغییرات را به همه کسانی که برنامه را اجرا میکنند، پخش کند.

این همچنین به این معنا نیست که جاوااسکریپت مشکلی ذاتی دارد که باعث شود وضعیت برنامه شما برای قابل پیشبینی شدن نیاز به غیرقابل تغییر بودن داشته باشد، هر توسعهدهندهای که قبل از React برنامههای فرانتاند نوشته باشد، این را به شما خواهد گفت.

این ادعای نسبتاً گیجکننده به سادگی به این معنا است که اگر از React استفاده کنید، برنامه شما در معرض شرایط مسابقه قرار دارد، اما غیرقابل تغییر بودن به شما امکان میدهد این درد را از بین ببرید. چرا؟ زیرا React خاص است.. این کتابخانه ابتدا و بیش از هر چیز بهعنوان یک کتابخانه رندرینگ بهینه طراحی شده است و مدیریت وضعیت تابع این هدف است، و بنابراین وضعیت مؤلفه از طریق یک زنجیره ناهمزمان از رویدادها (که به آن «اتصال داده یکطرفه» گفته میشود) مدیریت میشود که رندرینگ را بهینه میکنند اما شما کنترلی بر آن ندارید و باید به خاطر داشته باشید که وضعیت را مستقیماً تغییر ندهید...

با توجه به این زمینه، آسان است که ببینیم نیاز به تغییرناپذیری ارتباط کمی با جاوااسکریپت دارد و ارتباط زیادی با ریاکت: اگر شما مجموعهای از تغییرات وابسته به هم در برنامه تازه خود داشته باشید و هیچ راه آسانی برای فهمیدن وضعیت فعلی خود نداشته باشید، دچار سردرگمی خواهید شد، و بنابراین کاملاً منطقی است که از تغییرناپذیری برای دنبال کردن هر تغییر تاریخی استفاده کنید.

۳) شرایط مسابقه به طور قطعی بد هستند.

خب، ممکن است اگر شما از ریاکت استفاده میکنید چنین باشند. اما اگر یک فریمورک دیگر انتخاب کنید، آنها نادر هستند.

علاوه بر این، معمولاً شما مشکلات خیلی بزرگتری برای رسیدگی دارید… مشکلاتی مثل جهنم وابستگی. مثل یک کدبیس متورم. مثل این که CSS شما بارگذاری نمیشود. مثل یک فرآیند ساخت کند یا گیر کردن به یک بکاند تکتکه که تقریباً باعث غیرممکن شدن تکرار میشود. مثل توسعهدهندگان کمتجربه که نمیفهمند چه خبر است و باعث بهم ریختن اوضاع میشوند.

میدانی. واقعیت. اما هی، چه کسی به آن اهمیتی میدهد؟

۴) غیرقابل تغییر بودن استفاده از نوعهای مرجع را به منظور کاهش تاثیر عملکردی ردیابی هر تغییر وضعیت به کار میبرد.

چون جدی، اگر قرار است هر بار که وضعیت شما تغییر میکند چیزی را کپی کنید، بهتر است مطمئن شوید که در این کار هوشمند هستید.

۵) تغییرناپذیری به شما اجازه میدهد کارها را بازگردانید.

چون اه... این مهمترین ویژگی است که مدیر پروژه شما از شما خواهد خواست، درست است؟

۶) وضعیت تغییرناپذیر در ترکیب با وبسوکتها پتانسیلهای جالب زیادی دارد

آخرین مورد ولی نه از اهمیت کم، انباشت دلتاهای وضعیت یک دلیل بسیار قانعکننده ایجاد میکند همراه با وبسوکتها، که امکان مصرف آسان وضعیت به عنوان جریان رویدادهای تغییرناپذیر را فراهم میکند...

زمانی که این مفهوم برایتان روشن شود (وضعیت بهعنوان جریانی از رویدادها باشد -- نه یک مجموعه خام از سوابق که نمایانگر آخرین دیدگاه باشند)، دنیای تغییرناپذیر تبدیل به مکانی جادویی برای زندگی میشود. سرزمینی از شگفتیها و امکانات مبتنی بر رویداد که فراتر از خود زمان است. و وقتی به درستی انجام شود، این قطعاً میتواند برنامههای زمان واقعی را آسانتر کند، شما فقط جریان رویدادها را به همه علاقهمندان منتقل میکنید تا بتوانند نمایش خود از وضعیت کنونی را بسازند و تغییرات خود را مجدداً به جریان مشترک بنویسند.

اما در یک لحظه از خواب بیدار میشوید و متوجه میشوید که تمام شگفتی و جادویی که وجود دارد رایگان نیست. بر خلاف همکاران مشتاق شما، ذینفعان شما (بله، افرادی که به شما پرداخت میکنند) چندان به فلسفه یا مد اهمیت نمیدهند و خیلی به پولی که برای ساخت محصولی که بتوانند بفروشند میپردازند اهمیت میدهند. و نکته اصلی این است که کدنویسی برای تغییرناپذیری سختتر است و شکستن آن آسانتر، بعلاوه اینکه داشتن یک رابط کاربری تغییرناپذیر اگر پشتصحنهای برای پشتیبانی آن نداشته باشید چندان اهمیتی ندارد. وقتی (و اگر!) نهایتاً بتوانید ذینفعان خود را متقاعد کنید که باید رویدادها را از طریق یک فناوری پوش مانند وبسوکتها منتشر و مصرف کنید، متوجه میشوید که مقیاسپذیری در محیط تولید چقدر دردسرساز است.


حالا برای چند توصیه، اگر تصمیم بگیری آن را بپذیری.

انتخاب نوشتن جاوااسکریپت با استفاده از برنامهنویسی تابعی (FP) و غیرقابل تغییر بودن دادهها، همچنین انتخابی است برای بزرگتر، پیچیدهتر و دشوارتر کردن مدیریت کدهای برنامه شما. من قویاً توصیه میکنم این رویکرد را فقط در کاهندههای (reducers) Redux خود محدود کنید، مگر اینکه بدانید چه میکنید... و اگر قصد دارید با این وجود از غیرقابل تغییر بودن استفاده کنید، آنگاه حالت غیرقابل تغییر را به کل ساختار برنامه خود اعمال کنید، نه فقط سمت کلاینت. به هر حال، کمی معنایی ندارد که جلوی رابط کاربری غیرقابل تغییر داشته باشید و سپس آن را به یک پایگاه داده متصل کنید که همه رکوردها یک نسخه قابل تغییر دارند... شما دوباره به همان مشکلاتی برمیگردید که سعی در دوری از آن داشتید!

حالا، اگر به اندازه کافی خوششانس هستید که بتوانید در کارتان انتخابهایی داشته باشید، پس سعی کنید از خرد خود استفاده کنید (یا نکنید) و آنچه را که برای شخصی که به شما پول میدهد درست است انجام دهید. شما میتوانید این را بر اساس تجربه خود، حس درونیتان، یا اتفاقاتی که در اطراف شما میافتد پایهگذاری کنید (اگرچه اگر همه از React/Redux استفاده میکنند، یک دلیل معتبر وجود دارد که پیدا کردن منبع برای ادامه کارتان آسانتر خواهد بود).. یا به طور جایگزین، میتوانید از رویکردهای توسعه مبتنی بر رزومه یا توسعه مبتنی بر هیجان استفاده کنید. آنها ممکن است بیشتر با سبک شما مطابقت داشته باشند.

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


حالا بعد از این جلسهی خوددرمانی میخواهم اشاره کنم که این را به عنوان یک مقاله در وبلاگم اضافه کردهام => ثابت بودن در جاوااسکریپت: دیدگاه مخالف. اگر احساسات شدیدی دارید که میخواهید از سینهتان بیرون بیاورید، آزادانه میتوانید آنجا پاسخ دهید ;).

پاسخ شماره ۳

سؤال این است که چرا تغییرناپذیری اینقدر مهم است؟ چه اشکالی در تغییر دادن اشیاء وجود دارد؟ آیا این باعث ساده شدن امور نمیشود؟

در واقع، برعکس آن درست است: تغییرپذیری باعث پیچیدهتر شدن مسائل میشود، حداقل در درازمدت. بله، این کار نوشتن کد اولیه شما را آسانتر میکند چون میتوانید هرچیزی را هرجا که میخواهید تغییر دهید، اما وقتی برنامه شما بزرگتر میشود، این یک مشکل میشود - اگر یک مقدار تغییر کرد، چه چیزی آن را تغییر داده است؟

وقتی همه چیز غیرقابل تغییر میشود، یعنی دادهها دیگر به طور ناگهانی قابل تغییر نیستند. شما با اطمینان میدانید که اگر یک مقدار را به یک تابع منتقل کنید، در آن تابع قابل تغییر نیست.

به طور ساده: اگر از مقادیر تغییرناپذیر استفاده کنید، فهمیدن کدتان بسیار آسان میشود: هر کسی یک نسخهی منحصر به فرد* از دادههای شما میگیرد، بنابراین نمیتواند با آن دست ببرد و بخشهای دیگر کد شما را خراب کند. تصور کنید کار کردن در یک محیط چندنخی چقدر آسانتر میشود!

یادداشت ۱: بسته به کاری که انجام میدهید، احتمالاً هزینه عملکردی برای تغییرناپذیری وجود دارد، اما چیزهایی مانند Immutable.js تا حد امکان بهینهسازی میکنند.

یادداشت ۲: در صورت نامحتمل اینکه مطمئن نباشید، Immutable.js و const در ES6 معنیهای بسیار متفاوتی دارند.

در حالت معمولی، من میتوانستم فقط شیء را به آرایه اضافه کنم. چگونه میتوانم این را در این حالت انجام دهم؟ فروشگاه را حذف کرده و دوباره ایجاد کنم؟ اضافه کردن یک شیء به آرایه عملیات کمهزینهتری نیست؟ پینوشت: اگر این مثال روش درستی برای توضیح عدم تغییرپذیری نیست، لطفاً بفرمایید مثال عملی درست چیست.

بله، مثال خبری شما کاملاً خوب است و استدلال شما دقیقاً درست است: شما نمیتوانید تنها فهرست موجود خود را اصلاح کنید، بنابراین نیاز به ایجاد یک فهرست جدید دارید:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

پاسخ شماره ۴

اگرچه پاسخهای دیگر هم خوب هستند، برای پاسخ به سؤال شما درباره یک مورد کاربردی عملی (از نظرات پای پاسخهای دیگر) بیایید یک دقیقه از کد در حال اجرای شما خارج شویم و به پاسخ فراگیر درست جلوی چشم شما نگاه کنیم: git. چه اتفاقی میافتد اگر هر بار که یک commit ارسال میکنید دادهها را در مخزن بازنویسی کنید؟

حال ما وارد یکی از مشکلاتی میشویم که مجموعههای غیرقابل تغییر با آن روبرو هستند: حجیم شدن حافظه. گیت به اندازه کافی هوشمند است که هر بار که تغییری ایجاد میکنید، به سادگی نسخههای جدیدی از فایلها نسازد، بلکه صرفاً تغییرات را دنبال میکند.

در حالی که من چیز زیادی دربارهٔ عملکرد داخلی گیت نمیدانم، تنها میتوانم حدس بزنم که از استراتژی مشابهی با کتابخانههایی که به آنها ارجاع میدهید استفاده میکند: به اشتراکگذاری ساختاری. در پشت صحنه، کتابخانهها از تریها یا درختان دیگر استفاده میکنند تا تنها گرههایی را که متفاوت هستند دنبال کنند.

این استراتژی برای ساختارهای دادهای در حافظه نیز از نظر عملکرد نسبتاً مناسب است، زیرا الگوریتمهای شناختهشدهای برای عملیات روی درخت وجود دارند که در زمان لگاریتمی عمل میکنند.

یک مورد استفاده دیگر: فرض کنید میخواهید دکمهای برای بازگردانی در وباپ خود داشته باشید. با استفاده از نمایشهای غیرقابل تغییر دادههایتان، پیادهسازی چنین چیزی نسبتاً ساده است. اما اگر به تغییرات وابسته باشید، یعنی باید نگران کش کردن وضعیت جهان و انجام بهروزرسانیهای اتمی باشید.

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

پاسخ شماره ۵

سؤال این است که چرا تغییرناپذیری اینقدر مهم است؟ چه اشکالی در تغییر دادن اشیاء وجود دارد؟ آیا این باعث ساده شدن امور نمیشود؟

درباره تغییرپذیری

از نظر فنی، مشکلی در تغییرپذیری وجود ندارد. این سریع است و از حافظه استفاده مجدد میکند. توسعهدهندگان از ابتدا به آن عادت دارند (همانطور که به یاد دارم). مشکل در استفاده از تغییرپذیری و مشکلاتی است که این استفاده میتواند به همراه داشته باشد.

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

سردرد تغییرپذیری

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

مشکل بعدی که اغلب با جهش پیش میآید، وضعیت خراب است. وضعیت خراب میتواند زمانی رخ دهد که فرآیند جهش در میانه راه شکست بخورد و بعضی از فیلدها تغییر کرده باشند و بعضی نه.

علاوه بر این، با وجود تغییر (موتیشن) پیگیری تغییرات دشوار است. بررسی ساده مرجع تفاوتها را نشان نمیدهد، برای اینکه بفهمیم چه چیزی تغییر کرده است، نیاز به بررسی عمیقتری است. همچنین برای نظارت بر تغییرات، نیاز است یک الگوی قابل مشاهده معرفی شود.

در نهایت، جهش دلیل کسری اعتماد است. چگونه میتوانی مطمئن باشی که یک ساختار دارای مقدار مورد نظر است، اگر امکان تغییر آن وجود داشته باشد.

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

همانطور که مثال بالا نشان میدهد، عبور ساختار قابل تغییر همیشه میتواند با داشتن ساختار متفاوت به پایان برسد. تابع doSomething در حال تغییر ویژگیای است که از بیرون داده شده است. به کد اعتماد نکنید، شما واقعاً نمیدانید چه چیزی دارید و چه چیزی خواهید داشت. تمام این مشکلات به خاطر این است که: ساختارهای قابل تغییر نمایانگر اشارهگرها به حافظه هستند.

تغییرناپذیری مربوط به مقادیر است

ناپایداری به این معنی است که تغییر بر روی همان شیء یا ساختار انجام نمیشود، بلکه تغییر در یک شیء جدید نمایش داده میشود. و این به این دلیل است که مرجع نمایانگر مقدار است، نه تنها یک اشارهگر حافظه. هر تغییر یک مقدار جدید ایجاد میکند و به مقدار قدیمی دست نمیزند. چنین قوانین روشنی اعتماد و پیشبینیپذیری کد را بازمیگرداند. توابع استفاده از آنها امن است زیرا به جای تغییر، با نسخههای خود با مقادیر خود سروکار دارند.

استفاده از مقادیر به جای حافظههای ذخیرهسازی، اطمینان میدهد که هر شیء نمایانگر یک مقدار خاص و تغییرناپذیر است و استفاده از آن ایمن میباشد.

ساختارهای تغییرناپذیر نمایانگر مقادیر هستند.

من حتی بیشتر در این موضوع در مقاله مدیوم فرو میروم - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310