JavaScript — Primitive Vs Reference

Ye Min Ko
Learn Ng
Published in
6 min readAug 30, 2021
Photo by Michael Dziedzic on Unsplash

JavaScript နဲ့ code တွေရေးတဲ့အခါ တခါတလေ data type နဲ့ပတ်သက်ပြီး ကိုယ်ထင်ထားတဲ့အတိုင်း ဖြစ်မလာတာတွေ ဒါမှမဟုတ် ဘာဖြစ်နေမှန်းမသိ လိုချင်တဲ့အဖြေရမလာတာမျိုးတွေ ရှိနိုင်ပါတယ်။

ဒါကြောင့် ဒီ article မှာ JavaScript ရဲ့ primitive type နဲ့ reference type တွေမတူညီပုံ၊ memory မှာနေရာယူပုံတို့ကို နားလည်လွယ်အောင် ရှင်းပြမှာဖြစ်ပါတယ်။

Primitive Type တွေကတော့ boolean, number, string, undefined, null, symbol(ES6) တို့ဖြစ်ပါတယ်။

Reference Type တွေကတော့ Object, Array နဲ့ Function တို့ဖြစ်ပါတယ်။

Primitive Types

ပထမဆုံး primitive type တွေအလုပ်လုပ်ပုံကို ကြည့်ကြပါမယ်။ အောက်ပါ code ကိုကြည့်ပါ။

let a = "Hello";

ဒီလိုရေးလိုက်ရင် memory မှာ အခုလို နေရာယူသွားမှာ ဖြစ်ပါတယ်။

Primitive Types — 1

Value တွေထားဖို့ memory ပေါ်မှာ Stack နဲ့ Heap နှစ်ခုရှိတဲ့ထဲကမှ primitive type တွေကတော့ Stack ပေါ်မှာ နေရာယူပါတယ်။

အောက်ပါ code ကို ထပ်မံရေးလိုက်မယ် ဆိုရင်တော့…

let b = a;

b ထဲ a ကို assign လုပ်လိုက်ပါတယ်။ ဒါကြောင့် a ထဲက “Hello” value ကို b ထဲထည့်ပေးလိုက်ပြီး memory မှာ အခုလို နေရာယူသွားမှာ ဖြစ်ပါတယ်။

Primitive Types — 2

အကယ်၍ a ရဲ့ value ကို ဒီလိုပြောင်းလိုက်မယ်ဆိုရင် b ရဲ့ value က ဘာဖြစ်သွားမလဲ?

a = "Hi";
Primitive Types — 3

a ရဲ့ value ပြောင်းလိုက်တာကြောင့် Stack ပေါ်မှာ နောက်ထပ်နေရာ တစ်ခုထပ်ယူပြီး value အသစ်ထည့်လိုက်တာ တွေ့ရမှာပါ။ နဂို value အဟောင်းကတော့ point လုပ်တော့မှာ မဟုတ်တဲ့အတွက် ပျက်ပြယ်သွားမှာ ဖြစ်ပါတယ်။ b ကတော့ နဂိုအတိုင်းသာဆက်ရှိနေတာကြောင့် b ရဲ့ value သည် “Hello” ပဲ ဆက်ဖြစ်နေမှာ ဖြစ်ပါတယ်။

ဒီအတိုင်းပဲ b value ပြောင်းရင်လည်း အခုလို နေရာယူသွားမှာ ဖြစ်ပါတယ်။

Primitive Types — 4

တခြား primitive type တွေလည်း ဒီလိုပုံစံနဲ့ပဲ အလုပ်လုပ်ပါတယ်။

Objects

Reference type တစ်ခုဖြစ်တဲ့ object အလုပ်လုပ်ပုံကတော့ primitive type တွေနဲ့ကွဲပြားပါတယ်။ အောက်ပါ code ကိုကြည့်ပါ။

let a = { name: "Mg Mg"};

Memory မှာအခုလို နေရာယူပေးပါတယ်။

Objects — 1

Object တစ်ခုကို a ထဲထည့်တဲ့အခါ Stack ပေါ်မှာ Object ရဲ့ တည်နေရာဖြစ်တဲ့ memory address ကိုသိမ်းလိုက်ပါတယ်။ Object တွေက Heap ပေါ်မှာ ထားရတာဖြစ်လို့ ပုံပါအတိုင်း Heap ထဲမှာ သိမ်းလိုက်ပါတယ်။

ဒီနေရာမှာ သတိထားရမှာကတော့ primitive တုန်းက stack ပေါ်မှာ value ကိုတိုက်ရိုက်သိမ်းပေမဲ့ ဒီမှာကတော့ value အဖြစ် obj ရဲ့ address ကိုသာသိမ်းတာ ဖြစ်ပါတယ်။

ဆက်လက်ပြီး အခုလိုရေးမယ်ဆိုရင်တော့…

let b = a;
Objects — 2

b ထဲ a ကို assign လုပ်တဲ့အခါ a ရဲ့ value ဖြစ်တဲ့ address ကိုသာပေးလိုက်ပါတယ်။ Obj ပေးတာမဟုတ်ပါဘူး။

ဒါကြောင့် b က Stack ပေါ်မှာနေရာတစ်ခုယူပြီး obj ရဲ့ address ကိုမှတ်လိုက်ပါတယ်။ မှတ်လိုက်တဲ့ address က a နဲ့အတူတူပဲဖြစ်တာကြောင့် Heap ထဲက a ညွှန်ထားတဲ့ obj ကိုပဲ လှမ်းညွှန်လိုက်မှာ ဖြစ်ပါတယ်။

အကယ်၍ a ကအခုလို ပြောင်းလဲလိုက်ရင် b ပါလိုက်ပြောင်းမလား?

a.name = "Su Su";
Objects — 3

Heap ထဲက obj ရဲ့ name property ကိုပြောင်းလိုက်တာဖြစ်ပါတယ်။ b ကလည်း အဲ့ဒီ obj ကိုပဲထောက်ထားတာ ဖြစ်တဲ့အတွက် b ပါ လိုက်ပြောင်းသွားမှာ ဖြစ်ပါတယ်။

ဒါဆိုရင် ဒီလိုရေးရင်ရော b လိုက်ပြောင်းမှာလား?

a = { name: "Htoo" };
Object s— 4

Obj အသစ်တစ်ခုကို a ထဲ assign လုပ်လိုက်တာကြောင့် Heap ပေါ်မှာ Obj ကိုသိမ်းပြီး ထို obj ရဲ့ address ကို Stack ပေါ်မှာသိမ်းလိုက်တာတွေ့ရမှာပါ။ အရင် address အဟောင်းကတော့ ပျက်ပြယ်သွားမှာဖြစ်ပါတယ်။ ပုံအရဆိုရင် a နဲ့ b ကသီးခြားစီဖြစ်သွားတဲ့အတွက် a ရဲ့ပြောင်းလဲမှုကို b ကသိတော့မှာမဟုတ်ပါ။

ဒီ သဘောတရားက a မှမဟုတ်ပါဘူး b ကို b = { name: “Htoo”} ရေးရင်လည်းဒီလိုပဲ အလုပ်လုပ်ပေးမှာ ဖြစ်ပါတယ်။ ကိုယ်တိုင်စမ်းသပ်ကြည့်ပါ။

Objects with Arrays

Object ထဲမှာ array တွေနဲ့ တခြားသော object တွေပါတတ်ပါတယ်။ ဒီလိုပါလာခဲ့ရင် ဘယ်လိုအလုပ်လုပ်လဲ ဆိုတာ ဆက်ကြည့်ရအောင်ပါ။

အောက်ပါ code ကိုကြည့်ပါ။

let a = { 
name: "Mg Mg",
hobbies: ["Sports"]
}
let b = a;
Objects with Arrays — 1

Obj ထဲမှာ array တစ်ခုပါလာတဲ့အတွက် Heap ပေါ်မှာ array ကိုသိမ်းလိုက်ပြီး အဲ့ array ရဲ့ address ကိုတော့ obj ထဲမှာ မှတ်ထားလိုက်တာကို တွေ့ရမှာပါ။ ကျန်တာကတော့ သိပြီးသားတွေပါ။

a ကအခုလိုပြောင်းလဲလိုက်ရင် b ပါလိုက်ပြောင်းလဲမလား?

a.hobbies = ["Cooking"];
Objects with Arrays — 2

array ကိုပဲပြောင်းလိုက်တာမလို့ Heap ပေါ်မှာအသစ်ထပ်သိမ်းလိုက်တဲ့ address ကိုပြောင်းမှတ်လိုက်ပါတယ်။ b ကကြည့်မယ်ဆိုရင် ပြောင်းလဲသွားတဲ့ address ကိုမြင်နေရတာမလို့ b ပါ လိုက်ပြောင်းသွားမှာ ဖြစ်ပါတယ်။

Array အပြင် object တွေပါလာခဲ့ရင်လည်း ဒီလိုပဲအလုပ်လုပ်ပေးမှာ ဖြစ်ပါတယ်။

Arrays

Array တွေရဲ့အလုပ်လုပ်ပုံကို ဆက်လေ့လာရအောင်ပါ။ Array ကလည်း object type ပဲဖြစ်တဲ့အတွက် အလုပ်လုပ်ပုံတွေ အတူတူပဲဖြစ်နေမှာပါ။

let a = [1, 2, 3];

ဒီလိုရေးရင် memory မှာ အခုလို a နေရာယူပေးပါတယ်။

Arrays — 1

Object တုံကလိုပဲ Heap ပေါ်မှာ array ကိုသိမ်းပြီး address ကိုတော့ stack မှာမှတ်လိုက်ပါတယ်။ ဆက်လက်ပြီး …

let b = a;
Arrays — 2

b က a ရဲ့ address ကိုရယူလိုက်ပြီး တူညီတဲ့ array ကိုပဲ point လုပ်လိုက်ပါတယ်။

အခုလိုရေးရင် b ကပြောင်းလဲမှာလား?

a.push(4);
Arrays — 3

push() လုပ်လိုက်တာကြောင့် Heap ထဲက array မှာသွားပြင်လိုက်ပါတယ်။ b ကလည်း ဒီ array ကိုပဲ point လုပ်ထားတာမလို့ b ပါ လိုက်ပြောင်းသွားမှာ ဖြစ်ပါတယ်။

ဒီလိုရေးရင်ရော b လိုက်ပြောင်းမှာလား?

a = [100];
Arrays — 4

a ထဲကို array အသစ်ထည့်လိုက်တာဖြစ်လို့ Heap မှာ ထို array အသစ်ကိုသိမ်းလိုက်ပါတယ်။ ပြီးတော့ stack ပေါ်မှာ ထို array ရဲ့ address ကိုမှတ်လိုက်ပါတယ်။ နဂို array ရဲ့ address ကတော့အသုံးမပြုတော့လို့ ပျက်ပြယ်သွားပါတယ်။ ပုံကိုကြည့်မယ်ဆိုရင် a နဲ့ b က သီးခြားစီဖြစ်သွားလို့ b က a ရဲ့ပြောင်းလဲခြင်းတွေကို မသိတော့ပါ။

ဒီ သဘောတရားက b = [100] ရေးရင်လည်းအတူတူပဲဖြစ်မှာဖြစ်ပါတယ်။ ကိုယ်တိုင် လက်တွေ့ရေးသားကြည့်ပါ။

Arrays with Objects

Array ထဲမှာလည်း obj တွေ တခြား array တွေပါနိုင်ပါတယ်။ အောက်ပါ code ကိုကြည့်ပါ။

let a = [ 1, 2, { name: "Mg Mg"} ];
let b = a;
Arrays with Objects — 1

Array ထဲမှာ obj တစ်ခုပါလာတာဖြစ်လို့ Heap မှာ obj ကိုသိမ်းလိုက်ပြီး obj ရဲ့ address ကို array ထဲမှာ မှတ်လိုက်တာတွေ့ရမှာပါ။ ကျန်တာကတော့ သိပြီးသားတွေပဲဖြစ်ပါတယ်။

ဒီလိုရေးလိုက်ရင် b ပြောင်းလဲမှာလား?

a[2] = { age: 12 };
Arrays with Objects — 2

Array ထဲက obj ကိုပြင်လိုက်တဲ့အတွက် Heap မှာ obj အသစ်ကိုသိမ်းလိုက်ပြီး ထို obj ရဲ့ address ကို array မှာ update လုပ်လိုက်တာ တွေ့ရမှာပါ။ b ကနေကြည့်ရင် array ထဲက address ပြောင်းသွားတာမြင်ရတာဖြစ်လို့ b ပါ လိုက်ပြောင်းသွားမှာ ဖြစ်ပါတယ်။

ဒီနည်းအတိုင်းပဲ array ထဲမှာ array တွေထပ်ပါလာခဲ့ရင်လည်း ဒီလိုပဲအလုပ်လုပ်မှာဖြစ်ပါတယ်။

အခုဆိုရင် ဘယ်လိုရေးသားရင်တော့ reference လုပ်ထားတဲ့ variable မှာပြောင်းလဲမှုတွေဖြစ်ပြီး ဘယ်လိုရေးရင် မပြောင်းဘူးလဲဆိုတာ သိလောက်မယ် ထင်ပါတယ်။ လက်တွေ့စမ်းကြည့်ပြီး သေချာနားလည်အောင်လုပ်ပါ။

Shadow Copy

တချို့အခြေအနေတွေမှာ reference type တွေကို ပြောင်းလဲမှုတွေလုပ်တဲ့အခါ original variable ကိုမထိခိုက်စေချင်ရင် clone လုပ်ပြီးအသုံးပြုကြပါတယ်။ ဒီလိုလုပ်တဲ့အခါမှာ အောက်ပါနည်းတွေကိုသုံးရင် Shadow Copy ဖြစ်စေပါတယ်။

let a = { name: "Mg Mg", address: { street: "1st street"}}};
let b = {...a};
let c = Object.assign({}, a);

ဒီလို clone လုပ်ရင် memory မှာအခုလိုနေရာယူပေးပါတယ်။

Shadow Copy

Shadow Copy လုပ်လိုက်ရင် Obj ထဲက primitive type တွေကိုပဲ clone လုပ်ပေးတာဖြစ်ပြီး reference type တွေကိုတော့ နဂိုအတိုင်းညွှန်ပေးထားတာဖြစ်လို့ အကယ်၍ reference type ထဲက data ကိုပြင်မိရင် original variable ထဲမှာပါသွားပြီးသက်ရောက်စေမှာ ဖြစ်ပါတယ်။

Deep Copy

အကယ်၍ ကိုယ်က reference type တွေကိုပါ clone လုပ်စေချင်ရင် Deep Copy ဖြစ်အောင် အောက်ပါနည်းလမ်းကို သုံးနိုင်ပါတယ်။

let a = { name: "Mg Mg", address: { street: "1st street"}}};
let b = JSON.parse(JSON.stringify(a));

ဒီလိုရေးရင်တော့ memory မှာ အခုလို ဖြစ်သွားမှာပါ။

Deep Copy

Obj ရဲ့ primitive type တွေကိုပဲ clone လုပ်ပေးတာမဟုတ်တော့ပဲ obj ထဲကတခြား obj တွေနဲ့ array တွေကိုပါ သီးခြားထုတ်ပြီး clone လုပ်ပေးလိုက်ပါတယ်။ ဒါကြောင့် original variable ကို သက်ရောက်မှာ မဟုတ်တော့ပါ။

ဒီနည်းအတိုင်းပဲ Array တွေကို clone လုပ်ရင်လည်း အတူတူပဲဖြစ်ပါတယ်။

အခုဖော်ပြခဲ့တဲ့ Shadow Copy နဲ့ Deep Copy ပြုလုပ်နည်းတွေက သဘောတရားနားလည်အောင်သာ ဥပမာပေးတာ ဖြစ်ပြီး ဒီနည်းတွေပဲ အသုံးပြုလို့ရတာ မဟုတ်ပါ။

ဒါဆိုရင်တော့ အနည်းဆုံး data types တွေရဲ့ အလုပ်လုပ်ပုံကို သဘောပေါက်မယ်လို့ယူဆပါတယ်။ ပိုမိုသိရှိဖို့အတွက် အောက်ပါ References တွေကို ဆက်လက်လေ့လာပေးပါ။

--

--

Ye Min Ko
Learn Ng

🌐 Web Developer | 👀 Self-Learner | 👨‍💻 An Introvert