Mình là một dev front-end – một người code Javascript hơn 2 năm nay, mình đã nghĩ rằng mình hiểu những thứ cơ bản như tham trị và tham chiếu của JS cho đến ngày vừa rồi mình gặp một trường hợp khá buồn cười về shallow copy. Lúc này mình nhận ra mình đã hiểu sai về nó, vì thế mình viết bài này nhằm ghi chú lại kiến thức cũng như giúp đỡ anh em đang còn nhầm lẫn về vấn đề này :mrgreen: . Vào bài thôi!

Tham trị đối với primitive data type

Có 6 kiểu dữ liệu nguyên thủy (primitive data type): undefined, boolean, number, string, bigint, symbol. Khi ta sao chép giá trị biến này cho biến khác thì giá trị của 2 biến này độc lập và không liên quan với nhau.

Ví dụ nhé:

let a = 1
let b = a
b = 2
console.log(a) // 1
console.log(b) // 2

Dù gán b = a, nhưng khi b thay đổi thì a vẫn không thay đổi!

Khi giá trị thuộc kiểu dữ liệu nguyên thủy, biến sẽ chứa giá trị của biến đó.

Mô tả bộ nhớ ở ví dụ trên

Primivite data types

Tương tự với ví dụ dưới đây

let name = 'Du Thanh Duoc'
const change = (value) => {
  value = 'XdevClass'
}
change(name)
console.log(name) // 'Du Thanh Duoc'

Tham chiếu đối với Object

Anh em lưu ý là trong Javascript: object, array, function thì đều được coi là object cả nhé.

Khi gán hoặc sao chép dữ liệu thuộc kiểu object thì biến đó chỉ lưu địa chỉ của giá trị đó trên vùng nhớ. Nó không lưu giá trị được gán.

Gán Object

Ở trên ví dụ với object rồi, giờ ví dụ với array nhé

let cars1 = ['BMW', 'Mercedes']
let cars2 = cars1
cars2 = ['Toyota', 'Hyundai']
console.log(cars1) // ['BMW', 'Mercedes']
console.log(cars2) // ['Toyota', 'Hyundai']

Sơ đồ vùng nhớ

Tham trị và tham chiếu Javascript

add_1(car1) đại diện cho địa chỉ vùng nhớ của array ['BMW', 'Mercedes']

add_2(car2) đại diện cho địa chỉ vùng nhớ của array ['Toyota', 'Hyundai']

add_1(car1) khác add_2(car2)

Nhìn vào sơ đồ trên ta thấy được ban đầu thì car1car2 đều lưu giá trị ô nhớ giống nhau (đều là add_1(car1)) và chia sẽ cho nhau các thuộc tính bên trong array. car1car2 đều có giá trị ô nhớ giống nhau nhưng nó lại ở 2 mắt xích khác nhau, vì thế khi thay đổi car2 bằng 1 giá trị khác thì sẽ không ảnh hưởng đến car1. Nếu như bạn thay đổi car2[0] thì car1[0] cũng sẽ thay đổi theo vì 2 vị trí này chia sẽ với nhau.

Xem ví dụ bên dưới để rõ hơn nhé

Thay đổi thuộc tính Object

Ví dụ:

const person1 = { name: 'Du Thanh Duoc', age: 24 }
const person2 = person1
person2.age = 25
console.log(person1) // { name: 'Du Thanh Duoc', age: 25 }
console.log(person2) // { name: 'Du Thanh Duoc', age: 25 }

Bạn đang thắc mắc tại sao khai báo bằng const rồi nhưng lại có thể thay đổi giá trị bên trong object được phải không? Thực ra thì khi bạn khai báo const kết hợp với object thì bạn sẽ không thể thay đổi object nhưng lại có thể thay đổi thuộc tính của nó.

Như ví dụ thì bạn không thể thay đổi person2 bằng một object mới được, mà chỉ có thể thay đổi person2.age hoặc person2.name. Điều này liên quan đến vùng nhớ như mình đã nói ở bên trên, nếu bạn thay đổi nguyên 1 object thì vùng nhớ của person2 sẽ thay đổi và const  không cho phép nên sẽ gây lỗi. Nhưng nếu bạn thay đổi thuộc tính bên trong thì vùng nhớ object bên ngoài vẫn giữ nguyên.

Tham trị và tham chiếu Javascript

Như sơ đồ trên ta có thể thấy được person1person2 chia sẽ với nhau thuộc tính nameage, vì thế khi thay đổi age thì cả 2 sẽ đều bị thay đổi.

Shallow copy object

Ví dụ:

let person1 = { name: "Du Thanh Duoc", skill: { coding: "Javascript" } };
let person2 = { ...person1 };
console.log(person1.skill === person2.skill); //true
person2.skill = { coding: "HTML" };
console.log(person1.skill); // { coding: "Javascript" }
console.log(person2.skill); // { coding: "HTML" }
console.log(person1.skill === person2.skill); // false

Sơ đồ giải thích

Tham trị tham chiếu Javascript

Shallow Copy

Nhìn vào sơ đồ trên ta dễ dàng thấy được khi thực hiện shallow copy, Javascript sẽ tạo ra một object mới đến person2 sẽ lưu địa chỉ một ô nhớ mới. Nhưng vùng nhớ person1.skill sẽ giống person2.skill. Lưu ý là chúng chỉ giống thôi chứ không chia sẽ, nhìn trên hình ta thấy thì nó sẽ tách ra 2 nhánh khác nhau, chỉ có thuộc tính coding là chia sẽ giữa 2 object. Vậy nên khi thay đổi person2.skill thì person1.skill không bị thay đổi.

Nếu anh em thay đổi person2.skill.coding thì person1.skill.coding cũng sẽ bị đổi theo vì 2 thằng này chia sẽ với nhau.

Tóm lại

Thực ra kiến thức này được coi là căn bản thôi, nhưng mình nghĩ rằng nhiều anh em khi code với Javascript cũng sẽ dễ nhầm lẫn (điển hình là mình, ngại quá 😀 ). Hi vọng qua bài này mình đã giúp anh em hiểu rõ hơn về tham trị và tham chiếu trong Javascript một cách dễ dàng nhất.

Ngoài ra mình còn một số bài viết liên quan đến tham trị và tham chiếu nếu anh em muốn clean hơn trong các trường hợp thực tế thì nên xem qua.