JavaScript اور TypeScript آبجیکٹ ریفرنسز (object references) کو استعمال کے بعد ضائع شدہ سمجھنا خطرناک حد تک آسان بنا دیتے ہیں۔ آپ ایک آبجیکٹ بناتے ہیں، اسے کسی فنکشن کو بھیجتے ہیں، اسے کیش (cache) میں محفوظ کرتے ہیں، اور بعد میں اس ویری ایبل کو ایک نئے انسٹنس (instance) سے بدل دیتے ہیں۔ زبان اس پر کوئی اعتراض نہیں کرتی۔ پرانا ریفرنس آپ کے پروگرام میں کہیں اور اب بھی موجود ہوتا ہے، جو ایسے ڈیٹا کی طرف اشارہ کر رہا ہوتا ہے جو اب تازہ یا درست نہیں رہا۔ یہ کوئی کریش (crash) نہیں ہے۔ یہ اس سے بھی بدتر چیز ہے: آپ کے کوڈ بیس کے دو حصوں کے درمیان ایک خاموش فرق، جہاں دونوں حصے یہ سمجھتے ہیں کہ سچائی ان کے پاس ہے۔

اس سے بچنے کے لیے جو نظم و ضبط اپنایا جاتا ہے اسے Hard Object References کہا جاتا ہے۔ یہ کوئی لائبریری یا کمپائلر کا فیچر نہیں ہے۔ یہ ایک معاہدہ ہے جسے آپ اپنے پورے کوڈ پر نافذ کرتے ہیں۔

Stale Alias کا مسئلہ

Stale alias تب ہوتا ہے جب ایک ماڈیول کسی آبجیکٹ کا ریفرنس رکھتا ہے جبکہ دوسرا ماڈیول اس آبجیکٹ کو ایک نئے آبجیکٹ سے بدل دیتا ہے۔ پہلا ریفرنس اب بھی درست کوڈ ہے، لیکن یہ اب موجودہ ڈیٹا کی طرف اشارہ نہیں کرتا۔

ایک عام ویب ایپلی کیشن میں صارف کے ریکارڈ (user record) کا تصور کریں:

const user = {
  name: "Alice",
  address: {
    city: "Seoul",
    country: "KR"
  }
};

ایک شپنگ کمپوننٹ (shipping component) شروع میں ہی ایڈریس حاصل کر لیتا ہے:

const shippingAddress = user.address;

بعد میں، پروفائل اپ ڈیٹ آتی ہے۔ ایک reducer یا service handler پورے آبجیکٹ کو بدلنے کا فیصلہ کرتا ہے:

user.address = { city: "Tokyo", country: "JP" };

اس مقام پر، user.address ٹوکیو (Tokyo) کی طرف اشارہ کرتا ہے۔ لیکن shippingAddress اب بھی سیول (Seoul) والے پرانے آبجیکٹ کی طرف اشارہ کر رہا ہے۔ کوئی exception نہیں پھینکا جاتا۔ TypeScript خوش ہے کیونکہ types اب بھی میچ کر رہے ہیں۔ UI پروفائل پیج پر اپ ڈیٹ شدہ شہر دکھا سکتا ہے جبکہ شپنگ لیبل خاموشی سے پرانا شہر پرنٹ کر رہا ہوتا ہے۔ یہ بگ (bug) تب سامنے آتا ہے جب کوئی صارف شکایت کرتا ہے کہ اس کا پارسل غلط ملک چلا گیا ہے۔

ایسا اس لیے ہوتا ہے کیونکہ JavaScript شناخت (identity) کو ویلیو (value) سے الگ رکھتی ہے۔ جب آپ کسی آبجیکٹ پراپرٹی کو نئے object literal سے بدلتے ہیں، تو آپ اس زنجیر کو توڑ دیتے ہیں۔ پرانا آبجیکٹ ختم نہیں ہوتا؛ وہ محض یتیم (orphaned) ہو جاتا ہے۔ جو کوئی بھی اسے پکڑے ہوئے ہے، وہ ایک سایہ یا "بھوت" (ghost) کے ساتھ کام کر رہا ہے۔

Hard Object References کا مطلب کیا ہے

اصول سادہ ہے: primitive values کو بدلیں، لیکن کبھی بھی object یا array کے ریفرنسز کو نہ بدلیں۔ جب نیا ڈیٹا آئے، تو کنٹینر کو بدلنے کے بجائے اسے موجودہ کنٹینر میں کاپی کر لیں۔

اس کے لیے تین ٹھوس عادات کی ضرورت ہے۔

پہلا، آبجیکٹس اور ایریز (arrays) کو const کے ساتھ ڈکلیئر کریں۔ یہ ٹاپ لیول ویری ایبل کو نئے انسٹنس کے ساتھ دوبارہ باندھنے (rebind) کے لالچ کو ختم کر دیتا ہے۔ ویری ایبل کو اس کے scope کی مدت تک مستقل رہنا چاہیے۔

دوسرا، کبھی بھی ایسی پراپرٹی کو نہ بدلیں جو کسی آبجیکٹ یا ایرے کو رکھتی ہو، اسے کسی نئے بنائے گئے آبجیکٹ سے تبدیل نہ کریں۔ اگر آپ کو ایڈریس اپ ڈیٹ کرنے کی ضرورت ہے، تو اس کے اندر موجود پراپرٹیز کو تبدیل (mutate) کریں۔

تیسرا، اگر آپ کو اسٹیٹ (state) کو کلیئر یا ری سیٹ کرنے کی ضرورت ہے، تو اسے نئے خالی آبجیکٹ یا ایرے کے لیے چھوڑنے کے بجائے موجودہ ڈھانچے (structure) کو خالی کر دیں۔

ایڈریس کی مثال پر دوبارہ نظر ڈالتے ہوئے، درست اپ ڈیٹ کچھ اس طرح نظر آئے گی:

user.address.city = "Tokyo";
user.address.country = "JP";

اگر آنے والا ڈیٹا جزوی (partial) یا متحرک (dynamic) ہے، تو موجودہ ٹارگٹ میں لکھنے کے لیے Object.assign کا استعمال کریں:

Object.assign(user.address, incomingAddressData);

shippingAddress ویری ایبل، جو میموری میں بالکل اسی آبجیکٹ کی طرف اشارہ کرتا ہے، اب فوری طور پر نئے فیلڈز دیکھ لیتا ہے۔ صرف ایک ہی اصل (canonical) آبجیکٹ موجود ہے جو حقیقت کے زندہ ذریعے (live source of truth) کے طور پر کام کر رہا ہے۔

یہ کہاں سب سے زیادہ اہمیت رکھتا ہے

ایک سادہ (flat) کنفیگریشن آبجیکٹ کے لیے یہ نظم و ضبط ضرورت سے زیادہ معلوم ہو سکتا ہے۔ لیکن یہ اس وقت ضروری ہو جاتا ہے جب آپ کی اسٹیٹ ایک گراف (graph) کی شکل اختیار کر لے جہاں متعدد ذیلی نظام (subsystems) ایک دوسرے سے جڑے ہوئے نوڈز (nodes) کے پوائنٹرز رکھتے ہوں۔

ایک ریچ ٹیکسٹ ایڈیٹر (rich text editor) پر غور کریں۔ ڈاکومنٹ ماڈل نوڈز کا ایک درخت (tree) ہے۔ سلیکشن ماڈل شروع اور آخر کے نوڈز کے ریفرنسز رکھتا ہے۔ ہسٹری بفر ان نوڈز کے ریفرنسز رکھتا ہے جو پچھلے آپریشن میں تبدیل ہوئے تھے۔ رینڈرنگ لیئر ان نوڈز کے ریفرنسز رکھتی ہے جنہیں اس نے لے آؤٹ کے لیے ناپا تھا۔ اگر اسٹیٹ مینیجر کسی پیراگراف نوڈ کو نئے آبجیکٹ سے بدل دیتا ہے کیونکہ اس کا ٹیکسٹ تبدیل ہو گیا ہے، تو ان میں سے ہر ذیلی نظام اب ایک stale alias پکڑے ہوئے ہے۔ سلیکشن غلط حصے کو ہائی لائٹ کرتی ہے۔ ہسٹری سسٹم صحیح طریقے سے واپس (revert) نہیں جا سکتا۔ رینڈرر کریش ہو جاتا ہے یا، اس سے بھی بدتر، فرضی کرسرز (phantom cursors) دکھاتا ہے۔

یہی خطرہ ان یوزر پروفائلز میں بھی نظر آتا ہے جن میں نیسٹڈ (nested) سیٹنگز اور پرمیشنز ہوتی ہیں جن کا حوالہ UI، ایکسیس کنٹرول لیئر، اور آٹو سیو روٹین دیتی ہیں۔ یہ لے آؤٹ انجنوں میں بھی نظر آتا ہے جہاں پیرنٹ کنٹینرز چائلڈ نوڈز کی پیمائش کو کیش (cache) کرتے ہیں۔ یہ ویژول ایڈیٹرز اور کینوس ٹولز میں بھی نظر آتا ہے جہاں ایک رن ٹائم کنٹرولر ایکٹو اینٹیٹیز (active entities) کو ریفرنس کے ذریعے ٹریک کرتا ہے۔ ان تمام شعبوں میں، کمپوننٹس کسی آبجیکٹ کا ہینڈل (handle) پکڑتے ہیں اور توقع کرتے ہیں کہ وہ ہینڈل حقیقت کا ایک زندہ نظارہ (live view) رہے گا۔

Hard Object References آبجیکٹ کو ایک مستحکم پتے (stable address) کے طور پر لیتے ہیں۔ اندر کا فرنیچر بدل سکتا ہے، لیکن دروازہ اسی جگہ رہتا ہے۔ جو کوئی بھی پتہ جانتا ہے وہ اندر آ سکتا ہے اور موجودہ لے آؤٹ دیکھ سکتا ہے۔

ریپلیسمنٹ کے بجائے ری ایکٹیویٹی (Reactivity)

اگر آپ نے Redux یا اسی طرح کی immutable state libraries کے ساتھ کام کیا ہے، تو یہ ماڈل شاید الٹا معلوم ہوتا ہو۔ ان سسٹمز میں، تبدیلی کا اشارہ ایک نیا آبجیکٹ تیار کرنے سے ملتا ہے۔ ریفرنس کی تبدیلی ہی اصل اشارہ ہوتی ہے۔ Components یہ جاننے کے لیے کہ آیا انہیں دوبارہ رینڈر (re-render) کرنا ہے یا نہیں، prevProps.data === nextProps.data کا موازنہ کرتے ہیں۔

Hard Object References کے لیے آپ کو اس مفروضے کو بدلنے کی ضرورت ہوتی ہے۔ چونکہ ریفرنس مستقل رہتا ہے، اس لیے ریفرنس کی برابری (reference equality) آپ کو یہ نہیں بتا سکتی کہ ڈیٹا تبدیل ہوا ہے یا نہیں۔ آپ کو اپ ڈیٹس کو براڈکاسٹ کرنے کے لیے ایک مختلف طریقے کی ضرورت ہوتی ہے۔

عملی طور پر، اس کا مطلب ہے reactivity systems، explicit observers، یا dirty flags پر انحصار کرنا۔ user.address.city کو تبدیل (mutate) کرنا ایک ایسا setter ٹرگر کر سکتا ہے جو سبسکرائبرز (subscribers) کو مطلع کر دے۔ ایک آبجیکٹ event bus کے ذریعے تبدیلی کا ایونٹ (change event) جاری کر سکتا ہے۔ ایک game loop یا canvas tool ایک global dirty flag سیٹ کر سکتا ہے اور فریم کے اختتام پر گراف کو دوبارہ اسکین کر سکتا ہے۔ ریفرنس مستحکم رہتا ہے، اس لیے آپ کو دیگر طریقوں کے ذریعے ڈیٹا کے بہاؤ (data flow) کو ظاہر کرنا ہوگا۔

یہ طرزِ تعمیراتی تبدیلی (architectural shift) ہی وجہ ہے کہ یہ طریقہ کار پیچیدہ frontend state، بڑے component-local state، visual editors، canvas tools، اور runtime controllers کے لیے بہترین ہے۔ یہ سسٹمز پہلے ہی granular updates، direct mutations، یا imperative APIs پر انحصار کرتے ہیں۔ ان پر immutability کو زبردستی نافذ کرنے سے اکثر متناسب وضاحت حاصل کیے بغیر ضرورت سے زیادہ allocation pressure اور reference churn پیدا ہوتا ہے۔ جب ہر فریم اہمیت رکھتا ہو، تو صرف ایک سلائیڈر (slider) کو حرکت دینے کے لیے ایک نیا object graph مختص کرنا فضول ہے۔ ریفرنس کو hard رکھنا اور اندرونی حصے کو تبدیل (mutate) کرنا مسئلے کے اصل میکانزم کے عین مطابق ہے۔

اسے پختہ بنانا

اس اصول کے نظر انداز کیے گئے فوائد میں سے ایک یہ ہے کہ