9 lỗi JavaScript các lập trình viên hay gặp
Tác giả: Dipto Karmakar
JavaScript là ngôn ngữ script (ngôn ngữ kịch bản) được dùng để thêm các chức năng và tương tác trên web. Với những newbie từ các ngôn ngữ lập trình khác thì JavaScript khá dễ hiểu với một vài tutorial.
Khi bắt đầu với JavaScript, có một vài lỗi cơ bản mà nhiều lập trình viên hay gặp phải, bài viết này sẽ liệt kê 9 lỗi này và đề xuất solution cho từng loại.
Nhầm lẫn giữa assignment (=) và equality (==,===) operators
Đúng như tên gọi, assignment operator (=) được dùng để gán giá trị với biến. Dev hay nhầm nó với equality operator (toán tử). Ví dụ:
const name = "javascript";
if ((name = "nodejs")) {
console.log(name);
}
// output - nodejs
Tên biến và chuỗi ‘nodejs’ không được so sánh trong trường hợp này. Thay vào đó, chuỗi ‘nodejs’ được gán thành giá trị của biến và kết quả console trả về là ‘nodejs’.
Với JavaScript, biểu tượng 2 dấu bằng (==) và ba dấu bằng (===) được gọi là comparison operator (toán tử so sánh). Đoạn code phía trên là cách hợp lý để so sánh các giá trị:
const name = "javascript";
if (name == "nodejs") {
console.log(name);
}
// no output
// OR
if (name === "nodejs") {
console.log(name);
}
// no output
Sự khác nhau giữa những comparison operators này, 2 dấu bằng thì thể hiện loose comparison còn 3 dấu bằng thì là strict comparison. Trong loose comparison, thì chỉ so sánh mỗi các values (giá trị), nhưng với strict comparisons, thì so sánh giữa values (giá trị) và data type (kiểu dữ liệu).
Đoạn code phía dưới sẽ giải thích rõ hơn
const number = "1";
console.log(number == 1);
// true
console.log(number === 1);
// false
Biến số được gán với chuỗi giá trị là 1. Nên khi so sánh với 1 (hay kiểu number) với ==, nó sẽ trả về true bởi vì cả hai giá trị đều là 1.
Nhưng khi so sánh với ===, nó sẽ trả về false bởi mỗi giá trị có data type khác nhau.
Mong đợi callback xử lý synchronous (đồng bộ)
Callbacks là một trong những cách JavaScript xử lý các hàm bất đồng bộ. Promises và async/await là phương thức thích hợp để xử lý các hàm bất đồng bộ vì nếu dùng nhiều hàm callback (callback lồng vào nhau) sẽ dẫn đến callback hell.
Callback không xử lý đồng bộ, nó là chức năng được thực hiện sau khi một chức năng khác đã thực hiện xong.
Một ví dụ chứng minh là hàm setTimeout
sẽ sẽ gọi hàm callback như là lệnh đầu tiên và nhận lệnh thời gian (đơn vị ms) là lệnh thứ hai:
function callback() {
console.log("I am the first");
}
setTimeout(callback, 300);
console.log("I am the last");
// output
// I am the last
// I am the first
Sau 300 mili-giây, lệnh callback được gọi. Nhưng trước khi lệnh này hoàn thành thì dòng code còn lại sẽ chạy, đó là lý do vì sao lệnh console.log được chạy trước.
Dev hay nhầm callback là bất đồng bộ, ví dụ như callback trả về giá trị được dùng cho toán tử khác như sau:
function addTwoNumbers() {
let firstNumber = 5;
let secondNumber;
setTimeout(function () {
secondNumber = 10;
}, 200);
console.log(firstNumber + secondNumber);
}
addTwoNumbers();
// NaN
NaN
sẽ là output bởi vì secondNumber
không được xác định. Cái lúc chạy firstNumber + secondNumber
, secondNumber
vẫn là không xác định vì chức năng setTimeout
sẽ chạy sau lệnh callback 200ms
.
Cách tốt nhất để tiếp cận nó là thực hiện các dòng code còn lại với lệnh callback:
function addTwoNumbers() {
let firstNumber = 5;
let secondNumber;
setTimeout(function () {
secondNumber = 10;
console.log(firstNumber + secondNumber);
}, 200);
}
addTwoNumbers();
// 15
Xem thêm việc làm JavaScript lương cao tại TopDev
Sai references tới this
this
là khái niệm dễ nhầm lẫn nhất trong JavaScript. Để thành thục this
trong JS, bạn cần hiểu rõ cách nó hoạt động vì nó khá là khác so với những ngôn ngữ khác.
const obj = {
name: "JavaScript",
printName: function () {
console.log(this.name);
},
printNameIn2Secs: function () {
setTimeout(function () {
console.log(this.name);
}, 2000);
},
};
obj.printName();
// JavaScript
obj.printNameIn2Secs();
// undefined
Kết quả đầu tiên là JavaScript
vì this.name
trỏ chính xác về tên thuộc tính của object. Kết quả thứ hai sẽ là undefined
vì this
bị mất tham chiếu tới các thuộc tính của object (bao gồm cả tên).
Lý giải cho việc này là do this
phụ thuộc vào cái object gọi function chứa nó. Có từng biến this
trong từng function nhưng cái object nó trỏ tới sẽ được xác định bởi cái object mà đang gọi nó.
Cái this
trong obj.printName()
trỏ thằng tới obj.
Cái this
trong obj.printNameIn2Secs
trỏ thẳng tới obj
. Nhưng cái this
trong cái function callback setTimeout
sẽ không trỏ tới bất kỳ object nào bởi vì không có object gọi nó.
Với object được gọi là setTimeout
thì những cái như obj.setTimeout..
. sẽ được thực hiện. Vì không có object nào gọi cái function đó cho nên object mặc định (window
) sẽ được dùng.
name
không hiện diện trong window, dẫn tới undefined
Cách hay nhất để giữ các reference tới this
trong setTimeout
là dùng bind
, call
, apply
hay arrow function. Không giống với các function khác, arrow function không tự tạo this cho riêng nó. Cho nó ví dụ bên dưới sẽ giữ reference tới this
:
const obj = {
name: "JavaScript",
printName: function () {
console.log(this.name);
},
printNameIn2Secs: function () {
setTimeout(() => {
console.log(this.name);
}, 2000);
},
};
obj.printName();
// JavaScript
obj.printNameIn2Secs();
// JavaScript
Bỏ qua tính mutability của object
Không như những kiểu dữ liệu nguyên thủy như string, number, trong JS objects là kiểu dữ liệu tham chiếu. Ví dụ trong các object key-value:
const obj1 = {
name: "JavaScript",
};
const obj2 = obj1;
obj2.name = "programming";
console.log(obj1.name);
// programming
obj1
và obj2
có cùng tham chiếu tới vị trí memory chứa object đó.
Trong các mảng:
const arr1 = [2, 3, 4];
const arr2 = arr1;
arr2[0] = "javascript";
console.log(arr1);
// ['javascript', 3, 4]
Lỗi hay gặp là bỏ qua đặc tính này của JavaScript và dẫn tới một số error. Ví dụ, 5 object đều có cùng tham chiếu tới cùng object, một trong số object này có thể can thiệp vào thuộc tính codebase có quy mô large-scale.
Khi điều này xảy ra thì bất kỳ nỗ lực nào truy cập vào thuộc tính ban đầu sẽ trả về undefined hay thậm chí là xuất hiện error.
Cách giải quyết là luôn tạo một reference cho từng cái object mới khi muốn nhân bản một object, và rest operator (...
) là giải pháp thiết thực nhất.
Ví dụ, trong object key-value:
const obj1 = {
name: "JavaScript",
};
const obj2 = { ...obj1 };
console.log(obj2);
// {name: 'JavaScript' }
obj2.name = "programming";
console.log(obj.name);
// 'JavaScript'
Trong các mảng:
const arr1 = [2, 3, 4];
const arr2 = [...arr1];
console.log(arr2);
// [2,3,4]
arr2[0] = "javascript";
console.log(arr1);
// [2, 3, 4]
Lưu array và object trong browser storage
Đôi lúc với JS, lập trình viên sẽ tận dụng localStorage
để lưu các value. Nhưng lỗi hay mắc nhất ở đây là cố lưu arrays và object trong localStorage
, mà localStrorage
thì chỉ chấp nhận strings (chuỗi).
Để lưu object, JS chuyển object thành một string. Kết quả là [Object Object] cho các object và string được cách ra bởi dấu phẩy cho những thành tố array.
Ví dụ:
const obj = { name: "JavaScript" };
window.localStorage.setItem("test-object", obj);
console.log(window.localStorage.getItem("test-object"));
// [Object Object]
const arr = ["JavaScript", "programming", 45];
window.localStorage.setItem("test-array", arr);
console.log(window.localStorage.getItem("test-array"));
// JavaScript, programming, 45
Khi các object được lưu như thế này, sẽ rất khó khăn để access chúng. Với ví dụ object, access object như .name sẽ dẫn đến error. Bời vì [Object Object]
giờ là string mà không có thuộc tính name
.
Cách hay nhất để lưu objects và arrays trong local storage là dùng JSON.stringify
(để đổi objects thành strings) và JSON.parse (đổi strings thành objects). Như vậy việc access tới object sẽ trở nên dễ dàng.
Phiên bản đúng của đoạn code phía trên sẽ là:
const obj = { name: "JavaScript" };
window.localStorage.setItem("test-object", JSON.stringify(obj));
const objInStorage = window.localStorage.getItem("test-object");
console.log(JSON.parse(objInStorage));
// {name: 'JavaScript'}
const arr = ["JavaScript", "programming", 45];
window.localStorage.setItem("test-array", JSON.stringify(arr));
const arrInStorage = window.localStorage.getItem("test-array");
console.log(JSON.parse(arrInStorage));
// JavaScript, programming, 45
Không dùng những value mặc định
Set default value (giá trị mặc định) trong hàng ngàn variables là cách hay nhất để ngăn chặn các error không mong muốn. Ví dụ của một lỗi thường gặp:
function addTwoNumbers(a, b) {
console.log(a + b);
}
addTwoNumbers();
// NaN
Kết quả sẽ là NaN
bởi vì a
là undefined
và b
là undefined
. Nếu dùng default values thì có thể ngăn những error như thế này. Ví dụ:
function addTwoNumbers(a, b) {
if (!a) a = 0;
if (!b) b = 0;
console.log(a + b);
}
addTwoNumbers();
// 0
Thay vào đó, default value trong ES6 có thể được sử dụng:
function addTwoNumbers(a = 0, b = 0) {
console.log(a + b);
}
addTwoNumbers();
// 0
Ví dụ này tuy đơn giản nhưng nhấn mạnh tầm quan trọng của default value.
Đặt tên biến không phù hợp
Ừ đó, dev giờ vẫn mắc lỗi này. Đặt tên thì khá phức tạp nhưng không có lựa chọn nào khác. Comment là good practice trong lập trình nên đặt tên biến cũng vậy. Ví dụ:
function total(discount, p) {
return p * discount
}
Biến discount
thì cũng được nhưng còn p
hay total
? Total của cái gì? Cho nên:
function totalPrice(discount, price) {
return discount * price
}
Đặt tên biến phù hợp sẽ giúp những contributors dễ dàng nắm được cách hoạt động của project.
Kiểm tra giá trị boolean
const isRaining = false
if(isRaining) {
console.log('It is raining')
} else {
console.log('It is not raining')
}
// It is not raining
Thường thấy cách kiểm tra giá trị boolean như đoạn code phía trên, dù ổn nhưng error sẽ xuất hiện khi test một vài values.
Với JS, loose comparison của 0
và false
sẽ thành true,
còn 1
và true
sẽ thành true
. Điều này đồng nghĩa nếu isRaining
là 1
, isRaining
là true
.
Đây cũng là lỗi hay gặp trong objects. Ví dụ:
const obj = {
name: 'JavaScript',
number: 0
}
if(obj.number) {
console.log('number property exists')
} else {
console.log('number property does not exist')
}
// number property does not exist
Dù có thuộc tính number,
nhưng obj.number
trở về 0
. đó là value fals
e, vì vậy block else
được thực hiện.
Cho nên trừ khi bạn chắc về chuỗi value được dùng, boolean value và các properties trong object nên được test như thế này:
if(a === false)...
if(object.hasOwnProperty(property))...
Nhầm lẫn giữa Addition và Concatenation
Dấu cộng (+)
có hai chức năng trong JS: phép cộng hay nối. Cộng là dành cho số còn nối thì cho chuỗi, nhiều dev hay xài nhầm cái này. Ví dụ:
const num1 = 30;
const num2 = "20";
const num3 = 30;
const word1 = "Java"
const word2 = "Script"
console.log(num1 + num2);
// 3020
console.log(num1 + num3);
// 60
console.log(word1 + word2);
// JavaScript
Khi thêm chuỗi và số, JS chuyển số thành chuỗi, và nối tất cả value. Khi thực hiện cộng số thì diễn ra quá trình toán học rồi.
Kết luận
Dĩ nhiên là còn rất nhiều lỗi khác rất hay gặp, nên hãy luôn update bản thân thường xuyên. Học hỏi và phòng tránh các lỗi này sẽ giúp bạn build app và tool có giá trị hơn.
Bài viết gốc được đăng tải tại freeCodeCamp
Có thể bạn quan tâm:
- Xử lý lỗi nếu có xảy ra trong Javascript
- 3 lỗi javascript thường mắc phải làm ảnh hưởng perfomance
- 24 code ES6 tân tiến để khắc phục các lỗi thực hành JavaScript
Tìm việc IT lương cao, đãi ngộ tốt trên TopDev ngay!
- N Nâng tầm kỹ năng JavaScript: 6 khái niệm không thể bỏ qua
- J Jest là gì? Hướng dẫn thực hiện kiểm thử JavaScript với Jest
- S Strict Mode trong JavaScript – Sử dụng Strict Mode như thế nào cho tốt?
- T Tìm hiểu về Intl.RelativeTimeFormat trong JavaScript
- K Kinh nghiệm xử lý câu lệnh điều kiện trong JavaScript
- C Các cách xóa một property của Object trong Javascript
- K Kinh nghiệm truy xuất giá trị trong object lồng nhau trong Javascript
- H Hằng số và tính bất biến trong JavaScript
- T Thư viện Driver.js tạo hướng dẫn tương tác trang web
- C Các tips hiệu quả nhất khi làm việc với Javascript