Đôi điều về Object Reference trong Javascript. Nhiều lúc quên thật phiền toái!
Bài viết được sự cho phép của tác giả Tống Xuân Hoài
Vấn đề
Object Reference là một khái niệm thể hiện một biến tham chiếu, tức là thay vì lưu trữ giá trị thực nó lưu lại địa chỉ ô nhớ & thao tác với dữ liệu dựa trên địa chỉ đó. Điều này giúp tiết kiệm bộ nhớ cho các ứng dụng. Tuy nhiên cũng không ít điều phiền toái từ nó mà ra.
Kiểu dữ liệu nguyên thuỷ
Trong Javascript, chúng ta có những kiểu dữ liệu “nguyên thuỷ” (Primitive Types) bao gồm: undefined
, null
, string
, number
, boolean
và symbol
.
Các biến khi được khởi tạo với các giá trị này sẽ được cấp pháp một ô nhớ nhất định không có liên quan đến nhau. Ví dụ:
let name = "estacks";
let name2 = name; // estacks
name = "estacks.icu";
console.log(name); // estacks.icu
console.log(name2); //estacks
Bất kì khi nào thay đổi biến name thì những biến trước được gán bằng name
vẫn giữ nguyên giá trị. Điều này cũng tương tự với kiểu dữ liệu number
, boolean
…
Tham khảo tuyển dụng javascript lương cao trên TopDev
Object & Array
Không giống với Primitive Types, Object & Array, khi có nhiều hơn một biến được tạo để lưu trữ một object
, array
, function
. Những biến đó được trỏ đến địa chỉ đã được phân bổ tương tự trong bộ nhớ.
const arr1 = ['e', 's', 't', 'a', 'c', 'k', 's'];
const arr2 = arr1;
arr1[0] = 'a';
console.log(arr2); // ['a, 's', 't', 'a', 'c', 'k', 's']
Nhìn vào ví dụ trên, khi phần tử thứ nhất trong arr1
bị thay đổi, kéo theo đó thì arr2
cũng bị thay đổi theo. Tại sao vậy?
Khi arr1
được khai báo, bộ nhớ sẽ được cấp phát và một địa chỉ được lưu lại bởi nó. Sau đó arr2
được gán bằng arr1
. Vì arr1
là một mảng thay vì tạo mới một bản sao của mảng đó, arr2
chỉ đơn giản là trỏ đến địa chỉ lưu arr1
. Bằng cách đó, bất kỳ thay đổi nào được thực hiện trên arr1
cũng sẽ được thay đổi trên arr2
hoặc ngược lại bởi vì chúng trỏ đến cùng một vị trí. Điều này cũng tương tự với object
& function
.
Những phiền toái
“Quên” mất việc biến đang tham chiếu
Đây cũng là trường hợp mình thấy phổ biến nhất. Khai báo một biến dựa trên một biến khác mà quên mất biến đó có tính chất reference. Những bạn mới vào nghề sẽ rất dễ bị mắc lỗi này, vì thế khi biết được tính chất này thì hãy tránh khai báo một biến dựa trên một biến khác nhé, thay vào đó hãy sao chép nó ra.
const person1 = {
name: 'Nguyễn Văn A',
age: 20,
address: {
city: 'Hà Nội',
district: 'Cầu Giấy',
}
};
const person2 = { ...person1 };
Ở ví dụ trên mình vừa copy person1
sang person2
. Hãy thử thay đổi giá trị của name
hoặc age
ở person1
thì person2
vẫn không bị thay đổi. Nhưng nếu thay đổi city
hoặc district
thì person2 vẫn bị thay đổi theo. Lý do là bởi address
được khai báo với giá trị là object nên address
vẫn có tính chất reference.
Tham khảo việc làm JavaScript tại Hồ Chí Minh trên TopDev
Cách copy object bằng cú pháp spread (…) ở trên hay nhiều cách copy khác như dùng Object.assign
chỉ có thể sao chép “nông” (shallow) được đối tượng, để có thể sao chép được toàn bộ đối tượng lồng nhau như vậy mà tránh reference có thể dùng một trong ba cách sau:
Thứ nhất là dùng tổ hợp cú pháp JSON.parse
& JSON.stringify
. Đây là cách đơn giản & nhanh chóng nhất.
const person1 = {
name: 'Nguyễn Văn A',
age: 20,
address: {
city: 'Hà Nội',
district: 'Cầu Giấy',
}
};
const person2 = JSON.parse(JSON.stringify(person1));
Tuy nhiên đây lại là cách tồi nhất, do việc parse một string
thành object
hoàn toàn không tốt cho hiệu năng, chưa kể nếu như string đó là lớn.
Thứ hai là viết mã để thực hiện việc deep copy:
function deepCopy(obj) {
if(typeof obj !== 'object' || obj === null) {
return obj;
}
if(obj instanceof Date) {
return new Date(obj.getTime());
}
if(obj instanceof Array) {
return obj.reduce((arr, item, i) => {
arr[i] = deepCopy(item);
return arr;
}, []);
}
if(obj instanceof Object) {
return Object.keys(obj).reduce((newObj, key) => {
newObj[key] = deepCopy(obj[key]);
return newObj;
}, {})
}
}
Và cuối cùng, sử dụng thư viện có sẵn các hàm deepCopy
như lodash, ramda… hoặc dùng package clone có sẵn trên npm.
Đặt một đối tượng dùng chung
Hình dung như bạn có một config để sử dụng làm mặc định nếu không tìm thấy những config riêng của chúng, bạn sẽ export ra một object
chứa những config đó & thật tai hại nếu như trong quá trình sử dụng chúng, bạn vô tình thay đổi giá trị ở một nơi nào đó.
// file config.js
module.export = {
appName: "estacks",
connection: {
host: "0.0.0.0",
port: 80,
}
// file app.js
const conf = require('./config.js');
let config = findConfig(); // null
if (!config) config = conf;
...
// vô tình thay đổi conf
config.connection.port = 443;
Thì lúc này ở những file khác đang có import config.js thì connection.port
đều bị chuyển thành 443 hết.
Giải pháp cho vấn đề này là hãy deep clone config ra trước khi sử dụng. Để tránh việc thay đổi chúng sẽ dẫn đến những lỗi ngớ ngẩn mà có thể mất cả tuần để debug :D.
Kết luận
Kiến thức về Object Reference chỉ đơn giản là chỉ một biến tham chiếu. Khi làm việc với biến tham chiếu bạn phải hết sức cẩn thận để không phải mắc những sai lầm như tôi ở trên nhé!
Bài viết gốc được đăng tải tại 2coffee.dev
Có thể bạn quan tâm:
- Triển khai mã hiệu quả hơn với compose & pipe function trong Javascript
- Object Prototype Javascript – Công cụ hỗ trợ OOP cho JS
- 12 Thư viện JavaScript trực quan hoá dữ liệu hot nhất năm 2023
Xem thêm các việc làm công nghệ hấp dẫn trên TopDev
- G Giải Quyết Bài Toán Kinh Doanh Bằng Big Data và AI
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- F Framework nào tốt nhất cho dự án của bạn? – Checklist chi tiết
- K Kinh nghiệm xử lý responsive table hiệu quả
- S Stackoverflow là gì? Bí kíp tận dụng Stack Overflow hiệu quả
- 7 7 kinh nghiệm hữu ích khi làm việc với GIT trong dự án
- B Bài tập Python từ cơ bản đến nâng cao (có lời giải)
- B Bảo mật API là gì? Một số nguyên tắc và kỹ thuật cần biết
- H Hướng dẫn cài đặt và tự học lập trình Python cơ bản từ A-Z
- C Chinh Phục Phân Tích Dữ Liệu Với Pandas Trong Python: Hướng Dẫn Từng Bước