در جاوااسکریپت، متغیرها می‌توانند دو نوع داده را در خود نگه دارند که عبارتند از: مقادیر primitive و مقادیر reference. درک تفاوت بین این دو نوع داده برای نوشتن کد کارآمد و بدون اشکال بسیار مهم است. در این مقاله قصد داریم تا تفاوت بین مقادیر primitive و  reference در جاوااسکریپت را باهم بررسی کنیم.

منظور از مقادیر Primitive چیست؟

مقادیر Primitive داده‌هایی هستند که مستقیماً در یک متغیر ذخیره می‌شوند. این مقادیر شامل اعداد، بولین‌ها، رشته‌ها، null و undefined می‌باشند.

وقتی یک مقدار Primitive را به یک متغیر اختصاص می‌دهیم، یک کپی از آن مقدار ایجاد و در حافظه ذخیره می‌شود. هر تغییری که در متغیر ایجاد شود، بر مقدار اصلی تأثیر نمی‌گذارد. به عنوان مثال:

let x = 5;
let y = x;
y = 10;
console.log(x); // Output: 5
console.log(y); // Output: 10

در مثال بالا، به متغیر yیک کپی از مقدار متغیر xتخصیص داده شده است. وقتی مقدار y را تغییر می‌دهیم، روی مقدار x تاثیری نمی‌گذارد. این به این دلیل است که x و y متغیرهای جداگانه با مکان‌های حافظه جداگانه هستند.

منظور از مقادیر Reference چیست؟

مقادیر Reference آبجکت‌هایی هستند که در حافظه ذخیره می‌شوند و از طریق یک Reference قابل دسترسی می‌باشند. این مقادیر شامل آرایه‌ها، آبجکت‌ها و توابع هستند.

هنگامی که یک مقدار Reference را به یک متغیر اختصاص می‌دهیم، یک Reference به مقدار اصلی ایجاد شده و در حافظه ذخیره می‌شود. هر تغییری که در متغیر ایجاد شود بر مقدار اصلی تأثیر می‌گذارد. به عنوان مثال:

let array1 = [1, 2, 3];
let array2 = array1;
array2.push(4);
console.log(array1); // Output: [1, 2, 3, 4]
console.log(array2); // Output: [1, 2, 3, 4]

در مثال بالا، به متغیر array2یک Reference به آرایه اصلی array1اختصاص داده شده است. وقتی مقداری را به array2پوش می‌کنیم، روی array1نیز تأثیر می‌گذارد زیرا هر دو متغیر به یک مکان از حافظه اشاره می‌کنند.

چگونه مقادیر و Referenceها را به عنوان آرگومان تابع منتقل کنیم؟

وقتی یک مقدار primitive را به عنوان آرگومان به یک تابع ارسال می‌کنیم، یک کپی از آن مقدار ایجاد می‌شود و به تابع ارسال می‌گردد. هر تغییری که در متغیر داخل تابع ایجاد شود، بر مقدار اصلی تأثیر نمی‌گذارد. به عنوان مثال:

function addOne(x) {
    x++;
    return x;
}

let number = 5;
console.log(addOne(number)); // Output: 6
console.log(number); // Output: 5

در مثال بالا، تابع addOneیک کپی از مقدار numberدریافت می‌کند. وقتی مقدار xرا در داخل تابع افزایش می‌دهیم، روی مقدار numberتأثیری نمی‌گذارد.

هنگامی که یک مقدار reference را به عنوان آرگومان به یک تابع ارسال می‌کنیم، یک reference به مقدار اصلی ارسال می‌شود. هر تغییری که در متغیر داخل تابع ایجاد شود بر مقدار اصلی تأثیر می‌گذارد.

به عنوان مثال:

function addToArray(array) {
    array.push(4);
    return array;
}

let myArray = [1, 2, 3];
console.log(addToArray(myArray)); // Output: [1, 2, 3, 4]
console.log(myArray); // Output: [1, 2, 3, 4]

در مثال بالا، تابع addToArrayیک reference به آرایه اصلی myArrayدریافت می‌کند. وقتی در داخل تابع مقداری را به آرایه push کنیم، بر myArrayنیز تأثیر می‌گذارد زیرا هر دو متغیر به یک مکان از حافظه اشاره می‌کنند.

نحوه ایجاد یک کپی از آبجکت‌ها، آرایه‌ها و توابع

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

یکی از راه‌های ایجاد یک کپی سطحی از یک آبجکت، استفاده از سینتکس spread آبجکت است که در ECMAScript 2018 معرفی شد. سینتکس آن بسیار ساده بوده و به شکل زیر می‌باشد:

const originalObj = { name: "John", age: 30 };
const copyObj = { ...originalObj };

در این مثال، copyObjیک آبجکت جدید با ویژگی‌های مشابه originalObjاست. با این حال، تغییر copyObjبر originalObjتأثیری نخواهد گذاشت.

در مورد آرایه‌ها، می‌توانیم از متد slice()برای ایجاد یک کپی سطحی از یک آرایه استفاده کنیم. در ادامه یک مثال برای بررسی این موضوع داریم:

const originalArr = [1, 2, 3, 4];
const copyArr = originalArr.slice();

copyArrیک آرایه جدید با همان مقادیر originalArrاست.

زمانی که می‌خواهیم یک کپی از یک تابع ایجاد کنیم موضوع ممکن است کمی پیچیده‌تر باشد.یک رویکرد ایجاد یک تابع جدید است که به سادگی تابع اصلی را با همان آرگومان‌ها فراخوانی می‌کند. به عنوان مثال:

function originalFunc(arg1, arg2) {
    // function body here
}
const copyFunc = function(...args) {
    return originalFunc.apply(this, args);
};

در این مثال، copyFuncیک تابع جدید است که originalFuncرا با همان آرگومان‌ها فراخوانی می‌کند. اما باید این موضوع را به خاطر داشته باشیم که این کار فقط یک کپی سطحی از تابع ایجاد می‌کند. هر تابع یا آبجکت مورد استفاده در originalFuncهمچنان به مقادیر اصلی reference خواهند داشت.

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

مقایسه کپی‌های سطی و عمیق

در زبان برنامه نویسی جاوااسکریپت دو راه برای کپی کردن آبجکت‌ها و آرایه ها وجود دارد: کپی سطحی (shallow copy) و کپی عمیق (deep copy). درک تفاوت بین این دو نوع کپی مهم است، زیرا می‌تواند بر رفتار کد ما تأثیر بگذارد.

یک کپی سطحی یک آبجکت یا آرایه جدید ایجاد می‌کند، اما فقط referenceها به ویژگی‌های آبجکت یا آرایه اصلی را کپی می‌کند.

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

در ادامه یک مثال برای بررسی کپی سطحی از یک آرایه را بررسی می‌کنیم:

const originalArr = [1, 2, 3, [4, 5]];
const shallowCopyArr = [...originalArr];

shallowCopyArr[0] = 10;
shallowCopyArr[3][0] = 40;

console.log(originalArr); // [1, 2, 3, [40, 5]]
console.log(shallowCopyArr); // [10, 2, 3, [40, 5]]

در این مثال، عملگر spread برای ایجاد یک کپی سطحی از originalArrاستفاده می‌شود. سپس، اولین المنت shallowCopyArrبه ۱۰تغییر می‌کند که بر روی originalArrتأثیری نمی‌گذارد. اما وقتی اولین المنت آرایه تو در تو در shallowCopyArrبه ۴۰تغییر می‌کند، در آرایه originalArrنیز تغییر اتفاق می‌افتد، زیرا هر دو آرایه به یک آرایه تو در تو reference دارند.

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

در در ادامه یک نمونه از کپی عمیق از یک آرایه را بررسی می‌کنیم:

const originalArr = [1, 2, 3, [4, 5]];
const deepCopyArr = JSON.parse(JSON.stringify(originalArr));

deepCopyArr[0] = 10;
deepCopyArr[3][0] = 40;

console.log(originalArr); // [1, 2, 3, [4, 5]]
console.log(deepCopyArr); // [10, 2, 3, [40, 5]]

در این مثال برای تبدیل originalArrبه رشته از JSON.stringify()استفاده می‌کنیم، سپس برای تبدیل مجدد رشته به آرایه JSON.parse()را به کار می‌گیریم. این کار یک آرایه جدید با مقادیر مشابه originalArrایجاد می‌کند، اما آرایه جدید کاملاً مستقل از آرایه اصلی است.

در نتیجه، تفاوت اصلی بین یک کپی سطحی و کپی عمیق در جاوااسکریپت این است که آیا آبجکت یا آرایه جدید فقط referenceها به ویژگی‌های آبجکت یا آرایه اصلی را کپی می‌کند یا اینکه مقادیر این ویژگی‌ها را نیز شامل می‌شود.

جمع‌بندی

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

نکته‌ای که باید به یاد داشته باشیم این است که در جاوااسکریپت مقادیر تایپ‌های primitive همراه با value ارسال می‌شوند، در حالی که آبجکت‌ها و آرایه‌ها با reference ارسال می‌گردند. همچنین این موضوع را باید هنگام کار با توابع و تخصیص متغیرها نیز در نظر داشته باشیم.

شایان ذکر است که ECMAScript 6 یک کلمه کلیدی جدید به نام let معرفی کرد که بیشتر شبیه زبان‌های برنامه نویسی سنتی در رابطه با انتساب متغیر عمل می‌کند. let به ما این امکان را می‌دهد که یک متغیر با محدوده بلاک تعریف کنیم، به این معنی که متغیر فقط در داخل بلاک کدی که در آن تعریف شده است قابل دسترسی می‌باشد. این موضوع می‌تواند با محدود کردن دامنه متغیر، از ایجاد سردرگمی جلوگیری نماید.

در پایان، در حالی که مقادیر primitive و reference ممکن است جزییات کوچکی در طرح بزرگ برنامه نویسی جاوااسکریپت به نظر برسند، اما می‌توانند تأثیر قابل توجهی بر رفتار و کارایی کد ما داشته باشند. با درک تفاوت‌ها و استفاده از تکنیک‌های مناسب برای موقعیت خاص خود، می‌توانیم کدهای تمیزتر، مؤثرتر با خطای کم‌تر بنویسیم.