9 lỗi JavaScript các lập trình viên hay gặp

2195

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.

  JavaScript là gì? Internet có thể tồn tại mà không có JavaScript hay không?
  24 code ES6 tân tiến để khắc phục các lỗi thực hành JavaScript

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’.

  Những ngôn ngữ lập trình không nên học

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

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à JavaScriptthis.name trỏ chính xác về tên thuộc tính của object. Kết quả thứ hai sẽ là undefinedthis 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

obj1obj2 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.

Xem thêm: Dấu (…) trong JavaScript có ý nghĩa gì?

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ì aundefinedbundefined. 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 0false sẽ thành true, còn 1true sẽ thành true. Điều này đồng nghĩa nếu isRaining1, isRainingtrue.

Đâ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 false, 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.

Xem thêm: Convert giá trị String qua Number trong Javascript

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:

Xem thêm việc làm JavaScript lương cao tại TopDev

Báo cáo bài viết vi phạm bản quyền>>