ਤੁਸੀਂ ਇੱਕ ਬੱਗ ਨੂੰ ਤਿੰਨ ਫਾਈਲਾਂ ਵਿੱਚ ਲੱਭਦੇ ਹੋ, ਪਰ ਅੰਤ ਵਿੱਚ ਪਤਾ ਲੱਗਦਾ ਹੈ ਕਿ ਇੱਕ ਮਾਸੂਮ ਹੈਲਪਰ ਫੰਕਸ਼ਨ ਨੇ ਇੱਕ ਯੂਜ਼ਰ ਦਾ ਨਾਮ ਬਦਲ ਦਿੱਤਾ ਹੈ। ਤੁਸੀਂ ਅਸਲ ਨੂੰ ਕਦੇ ਛੋਹਿਆ ਹੀ ਨਹੀਂ ਸੀ। ਜਾਂ ਸ਼ਾਇਦ ਤੁਹਾਨੂੰ ਇਹੀ ਲੱਗਦਾ ਸੀ। JavaScript ਵਿੱਚ, ਇੱਕ ਵੇਰੀਏਬਲ ਨੂੰ ਦੂਜੇ ਨੂੰ ਅਸਾਈਨ ਕਰਨ ਦਾ ਮਤਲਬ ਹਮੇਸ਼ਾ ਉਹ ਨਹੀਂ ਹੁੰਦਾ ਜੋ ਤੁਸੀਂ ਉਮੀਦ ਕਰਦੇ ਹੋ। ਇਹ ਭਾਸ਼ਾ ਆਪਣੇ ਡੇਟਾ ਨੂੰ ਦੋ ਸਟੋਰੇਜ ਰਣਨੀਤੀਆਂ ਵਿੱਚ ਵੰਡਦੀ ਹੈ, ਅਤੇ ਇਹ ਭੁੱਲ ਜਾਣਾ ਕਿ ਤੁਸੀਂ ਕਿਸ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੇ ਹੋ, ਉਹੀ ਤਰੀਕਾ ਹੈ ਜਿਸ ਨਾਲ ਚੁੱਪਚਾਪ ਬਦਲਾਅ (mutations) ਪ੍ਰੋਡਕਸ਼ਨ ਕੋਡ ਵਿੱਚ ਆ ਜਾਂਦੇ ਹਨ।
ਇਸ ਤੋਂ ਬਚਣ ਲਈ, ਤੁਹਾਨੂੰ ਪ੍ਰੀਮੇਟਿਵ (primitive) ਵੈਲਯੂਜ਼ ਅਤੇ ਆਬਜੈਕਟਸ (objects) ਵਿਚਕਾਰ ਅੰਤਰ ਨੂੰ ਬਿਲਕੁਲ ਉਸੇ ਤਰ੍ਹਾਂ ਦੇਖਣ ਦੀ ਲੋੜ ਹੈ ਜਿਵੇਂ ਕਿ ਇੰਜਣ ਦੇਖਦਾ ਹੈ।
Primitives: ਅਸਲ ਕਾਪੀਆਂ
ਇੱਕ ਪ੍ਰੀਮੇਟਿਵ ਇੱਕ ਸਿੰਗਲ, ਅਣਵੰਡਣਯੋਗ ਡੇਟਾ ਹੈ। ਇਸ ਨੂੰ ਛੋਟੇ JavaScript ਵੈਲਯੂਜ਼ ਵਿੱਚ ਨਹੀਂ ਵੰਡਿਆ ਜਾ ਸਕਦਾ। ਇਹ ਭਾਸ਼ਾ ਸੱਤ ਪ੍ਰੀਮੇਟਿਵ ਕਿਸਮਾਂ ਨੂੰ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੀ ਹੈ: String, Number, Boolean, Undefined, Null, Symbol, ਅਤੇ BigInt।
ਕਿਉਂਕਿ ਪ੍ਰੀਮੇਟਿਵਸ ਐਟੋਮਿਕ (atomic) ਹੁੰਦੇ ਹਨ, ਉਹ ਆਮ ਤੌਰ 'ਤੇ ਸਿੱਧੇ ਵੇਰੀਏਬਲ ਬਾਈਂਡਿੰਗ ਦੇ ਅੰਦਰ ਹੁੰਦੇ ਹਨ। ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਪ੍ਰੀਮੇਟਿਵ ਵੇਰੀਏਬਲ ਨੂੰ ਦੂਜੇ ਵਿੱਚ ਕਾਪੀ ਕਰਦੇ ਹੋ, ਤਾਂ ਇੰਜਣ ਅਸਲ ਡੇਟਾ ਦੀ ਦੁਬਾਰਾ ਤਿਆਰੀ (duplicate) ਕਰਦਾ ਹੈ। ਹਰੇਕ ਵੇਰੀਏਬਲ ਨੂੰ ਮੈਮੋਰੀ ਵਿੱਚ ਆਪਣਾ ਸੁਤੰਤਰ ਸਲਾਟ ਮਿਲਦਾ ਹੈ।
let a = "Alina";
let b = a;
b = "Ali";
console.log(a); // "Alina"
console.log(b); // "Ali"
ਇੱਥੇ, a ਅਛੂਤਾ ਰਹਿੰਦਾ ਹੈ। b ਨੂੰ ਦੁਬਾਰਾ ਅਸਾਈਨ ਕਰਨ ਨਾਲ ਇੱਕ ਬਿਲਕੁਲ ਨਵੀਂ ਵੈਲਯੂ ਬਣੀ ਅਤੇ b ਉਸ ਵੱਲ ਇਸ਼ਾਰਾ ਕਰਨ ਲੱਗਾ, ਜਦੋਂ ਕਿ a ਅਜੇ ਵੀ ਆਪਣੀ ਅਸਲ ਸਟ੍ਰਿੰਗ ਨੂੰ ਫੜ ਕੇ ਰੱਖਦਾ ਹੈ। ਇਹ ਵਿਵਹਾਰ 'ਕਾਪੀ ਬਾਈ ਵੈਲਯੂ' (copy by value) ਹੈ। ਇਹ ਨੰਬਰਾਂ, ਬੂਲੀਅਨਾਂ, ਸਿੰਬਲਜ਼ ਅਤੇ ਪ੍ਰੀਮੇਟਿਵ ਪਰਿਵਾਰ ਦੇ ਬਾਕੀ ਮੈਂਬਰਾਂ ਲਈ ਵੀ ਬਿਲਕੁਲ ਉਸੇ ਤਰ੍ਹਾਂ ਕੰਮ ਕਰਦਾ ਹੈ। ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਫੰਕਸ਼ਨਾਂ ਵਿੱਚ ਪਾਸ ਕਰ ਸਕਦੇ ਹੋ, ਦੁਬਾਰਾ ਅਸਾਈਨ ਕਰ ਸਕਦੇ ਹੋ, ਜਾਂ ਸਾਈਡ ਇਫੈਕਟਸ ਦੀ ਚਿੰਤਾ ਕੀਤੇ ਬਿਨਾਂ ਰਿਟਰਨ ਕਰ ਸਕਦੇ ਹੋ।
Objects: ਸਾਂਝੇ ਐਡਰੈੱਸ
ਆਬਜੈਕਟਸ ਵੱਖਰੇ ਹੁੰਦੇ ਹਨ। ਇੱਕ ਆਬਜੈਕਟ ਇੱਕ ਕੰਪੋਜ਼ਿਟ ਕੰਟੇਨਰ ਹੈ ਜੋ ਡੇਟਾ ਦੇ ਕਈ ਹਿੱਸਿਆਂ ਨੂੰ ਇਕੱਠਾ ਕਰਦਾ ਹੈ। ਇਸ ਸ਼੍ਰੇਣੀ ਵਿੱਚ ਪਲੇਨ ਆਬਜੈਕਟਸ, ਐਰੇਜ਼ (arrays), ਫੰਕਸ਼ਨਾਂ, ਡੇਟਸ ਅਤੇ ਹਰ ਹੋਰ ਨਾਨ-ਪ੍ਰੀਮੇਟਿਵ ਕਿਸਮ ਸ਼ਾਮਲ ਹੈ। ਕਿਉਂਕਿ ਇਹ ਢਾਂਚੇ ਵੱਡੇ ਅਤੇ ਨੇਸਟਡ (nested) ਹੋ ਸਕਦੇ ਹਨ, JavaScript ਪੂਰੇ ਆਬਜੈਕਟ ਨੂੰ ਵੇਰੀਏਬਲ ਦੇ ਅੰਦਰ ਸਟੋਰ ਨਹੀਂ ਕਰਦਾ। ਇਸ ਦੀ ਬਜਾਏ, ਵੇਰੀਏਬਲ ਇੱਕ ਰੈਫਰੈਂਸ ਰੱਖਦਾ ਹੈ, ਜੋ ਅਸਲ ਵਿੱਚ ਕਿਸੇ ਹੋਰ ਥਾਂ ਸਟੋਰ ਕੀਤੇ ਅਸਲ ਡੇਟਾ ਵੱਲ ਇਸ਼ਾਰਾ ਕਰਨ ਵਾਲਾ ਮੈਮੋਰੀ ਐਡਰੈੱਸ ਹੁੰਦਾ ਹੈ।
ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਆਬਜੈਕਟ ਨੂੰ ਨਵੇਂ ਵੇਰੀਏਬਲ ਨੂੰ ਅਸਾਈਨ ਕਰਦੇ ਹੋ, ਤਾਂ ਇੰਜਣ ਐਡਰੈੱਸ ਨੂੰ ਕਾਪੀ ਕਰਦਾ ਹੈ, ਆਬਜੈਕਟ ਨੂੰ ਨਹੀਂ। ਹੁਣ ਦੋ ਵੇਰੀਏਬਲ ਬਿਲਕੁਲ ਇੱਕੋ ਘਰ ਵੱਲ ਇਸ਼ਾਰਾ ਕਰ ਰਹੇ ਹਨ।
const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";
console.log(user.name); // "Ali"
copy.name ਨੂੰ ਬਦਲਣ ਨਾਲ user.name ਵੀ ਬਦਲ ਗਿਆ ਕਿਉਂਕਿ ਦੋਵੇਂ ਨਾਮ ਇੱਕੋ ਅਸਲ ਆਬਜੈਕਟ ਵੱਲ ਇਸ਼ਾਰਾ ਕਰਦੇ ਹਨ। ਇਹ 'ਕਾਪੀ ਬਾਈ ਰੈਫਰੈਂਸ' (copy by reference) ਹੈ। ਇਹੀ ਹੈਰਾਨੀ ਐਰੇਜ਼ ਦੇ ਨਾਲ ਵੀ ਦਿਖਾਈ ਦਿੰਦੀ ਹੈ:
const scores = [82, 91, 74];
const backup = scores;
backup.push(88);
console.log(scores); // [82, 91, 74, 88]
ਮੈਮੋਰੀ ਵਿੱਚ ਸਿਰਫ਼ ਇੱਕ ਹੀ ਐਰੇ ਹੈ। scores ਅਤੇ backup ਸਿਰਫ਼ ਦੋ ਇਸ਼ਾਰੇ ਹਨ ਜੋ ਉਸ ਵੱਲ ਇਸ਼ਾਰਾ ਕਰ ਰਹੇ ਹਨ।
const ਕੀਵਰਡ ਉਲਝਣ ਦੀ ਇੱਕ ਹੋਰ ਪਰਤ ਜੋੜਦਾ ਹੈ। const ਨਾਲ ਇੱਕ ਆਬਜੈਕਟ ਨੂੰ ਡਿਕਲੇਅਰ ਕਰਨ ਨਾਲ ਵੇਰੀਏਬਲ ਬਾਈਂਡਿੰਗ ਲਾਕ ਹੋ ਜਾਂਦੀ ਹੈ ਤਾਂ ਜੋ ਤੁਸੀਂ ਇਸਨੂੰ ਨਵੇਂ ਐਡਰੈੱਸ ਵੱਲ ਨਹੀਂ ਮੋੜ ਸਕਦੇ। ਇਹ ਖੁਦ ਆਬਜੈਕਟ ਨੂੰ ਲਾਕ ਨਹੀਂ ਕਰਦਾ।
const settings = { theme: "dark" };
settings.theme = "light"; // Works perfectly.
settings = { theme: "dark" }; // TypeError
ਡਿਵੈਲਪਰ ਅਕਸਰ ਉਮੀਦ ਕਰਦੇ ਹਨ ਕਿ const ਅਪਰਿਵਰਤਨਸ਼ੀਲਤਾ (immutability) ਦਾ ਵਾਅਦਾ ਕਰਦਾ ਹੈ। ਇਹ ਨਹੀਂ ਕਰਦਾ। ਇਹ ਸਿਰਫ਼ ਰੈਫਰੈਂਸ ਦੇ ਦੁਬਾਰਾ ਅਸਾਈਨ ਹੋਣ ਤੋਂ ਰੋਕਦਾ ਹੈ। ਜੇਕਰ ਤੁਸੀਂ ਸਮੱਗਰੀ ਨੂੰ ਸੀਲ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਨੂੰ ਜਾਣਬੁੱਝ ਕੇ ਕਾਪੀ ਕਰਨੀ ਪਵੇਗੀ।
ਅਸਲ ਬੱਗ ਕਿੱਥੇ ਹੁੰਦੇ ਹਨ
ਰੈਫਰੈਂਸ ਬੱਗ ਬਹੁਤ ਘੱਟ ਵਾਰ ਹੀ ਸਪੱਸ਼ਟ ਵੇਰੀਏਬਲ ਰੀ-ਅਸਾਈਨਮੈਂਟ ਵਜੋਂ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ। ਉਹ ਫੰਕਸ਼ਨ ਕਾਲਾਂ ਦੇ ਅੰਦਰ ਲੁਕੇ ਹੁੰਦੇ ਹਨ।
function addTimestamp(record) {
record.timestamp = Date.now();
return record;
}
const original = { id: 1 };
addTimestamp(original);
console.log(original.timestamp); // A number now exists here. Oops.
ਪੈਰਾਮੀਟਰ record ਨੂੰ ਰੈਫਰੈਂਸ ਦੀ ਇੱਕ ਕਾਪੀ ਮਿਲੀ। ਫੰਕਸ਼ਨ ਦੇ ਅੰਦਰ ਕਿਸੇ ਵੀ ਪ੍ਰਾਪਰਟੀ ਦੇ ਬਦਲਾਅ ਨੇ ਸਿੱਧਾ ਕਾਲਰ ਦੇ ਆਬਜੈਕਟ ਵਿੱਚ ਲਿਖ ਦਿੱਤਾ। ਫੰਕਸ਼ਨ ਇੱਕ ਸਧਾਰਨ ਤਬਦੀਲੀ ਵਾਂਗ ਲੱਗ ਰਿਹਾ ਸੀ, ਫਿਰ ਵੀ ਇਸਨੇ ਸਕੋਪ ਦੀਆਂ ਸੀਮਾਵਾਂ ਦੇ ਪਾਰ ਸਟੇਟ ਨੂੰ ਲੀਕ ਕਰ ਦਿੱਤਾ।
ਇਹ ਪੈਟਰਨ UI ਫਰੇਮਵਰਕ ਜਿਵੇਂ ਕਿ React ਵਿੱਚ ਖਾਸ ਤੌਰ 'ਤੇ ਦਰਦਨਾਕ ਹੁੰਦਾ ਹੈ, ਜਿੱਥੇ ਸਟੇਟ ਅੱਪਡੇਟਸ ਨਿਰਭਰ ਕਰਦੇ ਹਨ