Top 9 Lỗi Phổ Biến Nhất Khi Code JavaScript Và Cách Khắc Phục

top 9 lỗi phổ biến nhất khi code JavaScript

JavaScript là một ngôn ngữ kịch bản, nó thường được sử dụng để thêm các chức năng và tăng tính tương tác cho trang web.

Đối với những bạn đã kinh nghiệm với ngôn ngữ lập trình khác, JavaScript là một ngôn ngữ khá dễ hiểu. Với một vài hướng dẫn tự học trên mạng, chúng ta có thể bắt đầu code JavaScript mà không gặp bất kỳ trở ngại nào hết.

Tuy nhiên, có một số lỗi phổ biến mà nhiều lập trình mới bắt đầu code JavaScript hay gặp phải.

Trong bài viết này, chúng ta sẽ liệt kê ra 9 lỗi phổ biến nhất khi code JavaScript và cách khắc phục để giúp bạn trở thành một lập trình viên JS tốt hơn.

1. Nhầm lẫn giữa toán tử gán (=) và toán tử so sánh bằng (==, ===)

Giống như tên gọi của nó, toán tử gán (=) được dùng để gán giá trị đến một biến (variable). Các lập trình viên thường nhầm lẫn nó với toán tử so sánh bằng (==, ===). Ví dụ:

const name = "javascript";
if ((name = "nodejs")) {
    console.log(name);
}
// output - nodejs

Biến name và giá trị "nodejs" không được so sánh với nhau trong trường hợp này. Thay vào đó, giá trị "nodejs" được gán đến biến name và console hiển thị kết quả giá trị "nodejs".

Trong JavaScript, double equal sign (==) và triple equal sign (===) được gọi là toán tử so sánh (comparison operator).

Đối với đoạn code ở trên, đây là cách thích hợp để so sánh các giá trị với nhau:

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 hai toán tử so sánh double equal sign (==) và triple equal sign (===) là:

  • Double equal sign thực hiện phép so sánh lỏng lẻo (loose comparison).
  • Triple equal sign thực hiện phép so sánh chặt chẽ (strict comparison).

Trong loose comparison, chỉ có giá trị được so sánh với nhau. Nhưng trong strict comparison, cả giá trị và kiểu dữ liệu được so sánh với nhau.

Chúng ta sẽ thấy rõ hơn trong đoạn ví dụ bên dưới:

const number = "1";
console.log(number == 1);
// true
console.log(number === 1);
// false

Biến number được gán giá trị là "1" (kiểu dữ liệu chuỗi). Khi  biến number được so sánh với 1 (kiểu dữ liệu số) bằng cách sử dụng toán tử ==, thì nó trả về kết quả true bởi vì cả hai đều có giá trị bằng 1.

Nhưng khi dùng toán tử === để so sánh, nó trả về kết quả false bởi vì mỗi giá trị có kiểu dữ liệu khác nhau.

2. Xử lý các callbacks một cách đồng bộ

Callbacks là cách mà JavaScript xử lý các hoạt động bất đồng bộ (asynchronous).

JavaScript cung cấp các method Promises và async/await là những method thích hợp để xử lý bất đồng bộ.

Ví dụ hàm global setTimeout nhận một callback là tham số đầu tiên và khoản thời gian (tính bằng mili giây) là tham số 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 thì callback được thực thi. Nhưng trước khi nó được thực thi, thì các đoạn code còn lại sẽ bắt đầu được chạy tiếp. Vì vậy, đây là nguyên nhân tại sao đoạn code console.log("I am the last") được chạy trước tiên.

Ngoài ra chúng ta có một ví dụ khác:

function addTwoNumbers() {
​​    let firstNumber = 5;
​​    let secondNumber;
​​    setTimeout(function () {
​​        secondNumber = 10;
​​    }, 200);
​​    console.log(firstNumber + secondNumber);
​​}
​​addTwoNumbers();
​​// NaN

Kết quả cuối cùng của ví dụ trên là NaN, bởi vì giá trị của secondNumber chưa được định nghĩa.

Tại thời điểm chạy đoạn code console.log(firstNumber + secondNumber), secondNumber vẫn chưa được định nghĩa. Nguyên nhân là hàm setTimeout thực thi callback sau 200ms.

Cách tốt nhất để khắc phục vấn đề này là chuyển đoạn code console.log(firstNumber + secondNumber) vào trong callback:

function addTwoNumbers() {
​​    let firstNumber = 5;
​​    let secondNumber;
​​    setTimeout(function () {
​​        secondNumber = 10;
​​        console.log(firstNumber + secondNumber);
​​    }, 200);
​​}
​​addTwoNumbers();
​​// 15

3. Tham chiếu sai this

this là một khái niệm thường bị hiểu nhầm trong JavaScript.

Để sử dụng được this trong JavaScript, bạn phải thực sự hiểu nó hoạt động như thế nào, bởi vì this trong JavaScript có một vài điểm khác biệt so với các ngôn ngữ lập trình 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 bởi vì this.name trỏ chính xác đến property name của obj.

Kết quả thứ hai là undefined bởi vì this không còn tham chiếu đến obj.

Nguyên nhân là tham chiếu của this trong một function phụ thuộc vào object gọi function đó.

Ở ví dụ trên, ta đều có biến this trong mỗi function nhưng this tham chiếu đến các object khác nhau khi function tương ứng được gọi.

Khi bạn dùng this trong obj.printName() thì tham chiếu của thisobj.

Tuy nhiên nếu bạn dùng this trong function callback của setTimeout thì this không còn tham chiếu đến obj, mà nó tham chiếu đến object gọi function callback, đó là window.

Tại vì property name không được khai báo trong window, nên kết quả là undefined.

Cách tốt nhất để this tham chiếu đến obj trong setTimeout là sử dụng bind, call, apply hoặc arrow functions (tính năng của ES6).

Không giống như những function bình thường khác, arrow function giúp this luôn tham chiếu đến object chứa nó.

Ta có thể thay đổi đoạn code ở trên lại như sau:

​​const obj = {
​​    name: "JavaScript",
​​    printName: function () {
​​        console.log(this.name);
​​    },
​​    printNameIn2Secs: function () {
​​        setTimeout(() => {
​​            console.log(this.name);
​​        }, 2000);
​​    },
​​};
​​obj.printName();
​​// JavaScript
​​obj.printNameIn2Secs();
​​// JavaScript

4. Không để ý đến sự thay đổi của object

Không giống như những kiểu dữ liệu nguyên thuỷ như string, number,… Objects trong JavaScript là kiểu dữ liệu tham chiếu. Ví dụ:

const obj1 = {
​​    name: "JavaScript",
​​};
​​const obj2 = obj1;
​​obj2.name = "programming";
​​console.log(obj1.name);
​​// programming

obj1obj2 có tham chiếu đến cùng một vị trí bộ nhớ, nơi chứa đựng thông tin của object.

Đối với mảng:

const arr1 = [2, 3, 4];
​​const arr2 = arr1;
​​arr2[0] = "javascript";
​​console.log(arr1);
​​// ["javascript", 3, 4]

Một trong những lỗi phổ biến của lập trình viên là không để ý đến bản chất này của JavaScript và điều này dẫn đến các kết quả không mong muốn.

Ví dụ khi có 5 objects tham chiếu đến cùng một object, nếu chúng ta thay đổi giá trị nào đó của object bất kỳ thì các object còn lại cũng thay đổi theo. Và nếu bạn không để ý đến điều này thì hệ thống rất dễ xảy ra lỗi và khó tìm ra nguyên nhân bắt nguồn từ đâu.

Cách tốt nhất để khác phục vấn đề này là tạo một tham chiếu mới khi chúng ta muốn nhân bản một object.

Để làm điều này, rest operator ( có trong ES6) là giải pháp hoàn hảo.

Ví dụ đối với objects:

​​const obj1 = {
​​    name: "JavaScript",
​​};
​​const obj2 = { ...obj1 };
​​console.log(obj2);
​​// {name: 'JavaScript' }
​​obj2.name = "programming";
​​console.log(obj.name);
​​// 'JavaScript'

Đối với arrays:

const arr1 = [2, 3, 4];
​​const arr2 = [...arr1];
​​console.log(arr2);
​​// [2,3,4]
​​arr2[0] = "javascript";
​​console.log(arr1);
​​// [2, 3, 4]

5. Lưu trữ arrays và objects trong browser storage

Khi làm việc với JavaScript, chúng ta thỉnh thoảng dùng localStorage để lưu trữ dữ liệu.

Tuy nhiên một lỗi phổ biến là dùng localStorage để lưu trữ arrays và objects. localStorage chỉ có thể lưu trữ strings.

Khi chúng ta cố gắng lưu trữ objects trong localStorage. JavaScript sẽ chuyển đổi object thành string. Kết quả là object chuyển thành string [Object Object], và tương tự array cũng chuyển thành comma-separated string.

​​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 objects được lưu trữ như vậy, thì thật khó để truy cập các giá trị của chúng. Ví dụ chúng ta truy cập một property của object với đoạn code .name thì kết quả là lỗi, bởi vì [Object Object] là string.

Để khắc phục điều này, chúng ta sử dụng JSON.stringify để chuyển đổi objects thành strings rồi sau đó mới lưu trữ vào localStorage.

Khi muốn lấy objects từ localStorage ra sử dụng, chúng ta dùng JSON.parse để chuyển đổi strings thành objects.

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

6. Không sử dụng giá trị mặc định

Thiết lập các giá trị mặc định cho variables là good practice để ngăn ngừa các lỗi không mong muốn. Ví dụ bên dưới là lỗi phổ biến:

function addTwoNumbers(a, b) {
​​    console.log(a + b);
​​}
​​addTwoNumbers();
​​// NaN

Kết quả là NaN bởi vì a có giá trị undefinedb cũng vậy. Bằng cách thiết lập giá trị mặc định, chúng ta có thể tránh được lỗi ở ví dụ trên:

function addTwoNumbers(a, b) {
​​    if (!a) a = 0;
​​    if (!b) b = 0;
​​    console.log(a + b);
​​}
​​addTwoNumbers();
​​// 0

Ngoài ra, chúng ta cũng có thể sử dụng tính năng giá trị mặc định có trong ES6:

​​function addTwoNumbers(a = 0, b = 0) {
​​    console.log(a + b);
​​}
​​addTwoNumbers();
​​// 0

7. Đặt tên biến không rõ ràng

Quá trình đặt tên biến không phải lúc nào cũng dễ dàng. Nhiều khi chúng ta thường đặt tên biến không rõ ràng, gây khó khăn trong việc bảo trì code hoặc sửa lỗi sau này.

function total(discount, p) {
​​    return p * discount
​​}

Biến discount được đặt tên dễ hiểu, tuy nhiên còn ptotal? Chúng ta nên sửa lại cho rõ ràng hơn như bên dưới:

function totalPrice(discount, price) {
​​    return discount * price
​​}

Việc đặt tên biến rõ ràng vô cùng quan trọng bởi vì điều này giúp các lập trình viên khác hiểu code của bạn dễ dàng hơn trong tương lai.

8. Kiểm tra các giá trị boolean

const isRaining = false
​​if(isRaining) {
​​    console.log('It is raining')
​​} else {
​​    console.log('It is not raining')
​​}
​​// It is not raining

Khi muốn kiểm tra giá trị boolean, chúng ta thường viết code như ví dụ ở trên. Mặc dù ví dụ này chạy đúng, nhưng nó sẽ xảy ra lỗi khi chúng ta thử với một vài giá trị khác.

Trong JavaScript, khi so sánh (loose comparison) giữa 0false thì kết quả là true, giữa 1true thì kết quả là true. Điều này có nghĩa là nếu isRaining bằng 1, isRaining sẽ bằng true.

Hãy xem ví dụ bên dưới:

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

Mặc dù property number có tồn tại, obj.number trả về giá trị 0, điều này có nghĩa là obj.number bằng false, do đó đoạn code trong else được thực thi chứ không phải đoạn code trong if.

Vì vậy để kiểm tra các giá trị boolean, chúng ta nên dùng toán tử === (strict comparison) để tránh phát sinh các lỗi không mong muốn:

if(a === false)...

9. Nhầm lẫn giữa Addition (Cộng) và Concatenation (Nối)

The plus sign (+) có hai chức năng chính: addition (cộng) và concatenation(nối). Addition là cho numbers còn concatenation là cho strings. Một số lập trình viên thường sử dụng sai toán tử 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 cộng strings với numbers, JavaScript chuyển đổi numbers thành strings, sau đó nối chúng lại với nhau.

Khi cộng numbers với numbers, JavaScript thực hiện phép cộng toán học như bình thường.

Kết luận

Qua bài viết này, chúng ta biết được các lỗi phổ biến nhất khi code JavaScript và cách khắc phục. LetDiv hy vọng bạn sẽ cải thiện được khả năng code, xây dựng các ứng dụng hiệu quả và đáng tin cậy hơn.

5 2 votes
Đánh giá
guest
0 Bình luận
Inline Feedbacks
View all comments
0
Nếu bạn có thắc mắc gì hãy để lại bình luận nhé!!!x