Menu

Console và DevTools JavaScript: debug hơn cả console.log

Các phương thức của console và tính năng DevTools giúp debug JavaScript nhanh hơn nhiều so với rải console.log khắp nơi.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Console không chỉ có log

console.log là công cụ debug đầu tiên mà hầu hết mọi người học, và cũng là công cụ mà nhiều người không bao giờ bỏ. Nó vẫn dùng được, nhưng đối tượng console còn có hàng chục phương thức khác giúp debug nhanh và rõ ràng hơn. Và khi đã quen với chính DevTools (breakpoint, call stack, watch expression), bạn sẽ ít cần đến log hơn trước.

Bắt đầu bằng một lượt xem nhanh:

Cả bốn đều in ra cùng một console, nhưng trình duyệt hiển thị chúng khác nhau: cảnh báo có nền vàng, lỗi có màu đỏ kèm biểu tượng, và cả hai đều kèm stack trace. Bộ lọc trong DevTools cho phép ẩn hoặc chỉ xem từng mức, rất cần thiết khi ứng dụng sinh ra hàng trăm thông điệp.

In nhiều giá trị theo cách lười

Khi muốn xem nhiều biến cùng lúc, đừng nối chúng thành một chuỗi. Hãy truyền chúng thành các đối số riêng, hoặc tốt hơn nữa, gói chúng vào một đối tượng để mỗi giá trị đều có nhãn:

Mẹo { user, count } dùng cú pháp viết tắt của đối tượng: tên biến trở thành khóa. Trong console bạn sẽ thấy { user: {...}, count: 3 } và có thể mở rộng user để xem chi tiết. Bạn không làm mất cấu trúc của đối tượng như khi chuyển nó thành chuỗi.

console.table cho mảng các đối tượng

Khi nhìn vào một mảng các đối tượng, console.log chỉ cho bạn một mớ thu gọn khó đọc. console.table hiển thị nó thành một bảng thật sự:

Trong console của trình duyệt, lần gọi đầu tiên in ra cả ba hàng với các cột có thể sắp xếp. Lần gọi thứ hai chỉ giữ lại name và role. Đây là một nâng cấp rất đáng giá cho mọi thứ có dạng dữ liệu: response từ API, kết quả truy vấn, tệp CSV đã phân tích.

console.dir và console.log

Trông thì giống nhau, nhưng chúng xử lý phần tử DOM khác nhau:

const el = document.querySelector("button");

console.log(el);   // prints the HTML: <button>Click me</button>
console.dir(el);   // prints the JS object view with all properties

log định dạng phần tử dưới dạng HTML. dir cho bạn xem chính đối tượng đó: mọi thuộc tính, mọi trình xử lý sự kiện, mọi tham chiếu style đã tính. Khi muốn biết một phần tử có những phương thức hay thuộc tính nào, hãy dùng dir.

Gom nhóm các thông điệp liên quan

Một phiên debug dài sẽ làm console đầy nhiễu. console.group và console.groupEnd gói các thông điệp liên quan vào một khối có thể thu gọn:

Mỗi lần gọi tạo ra một nhóm có tên mà bạn có thể thu gọn. Dùng console.groupCollapsed nếu muốn các nhóm thu gọn sẵn ngay từ đầu, tiện khi bạn chỉ muốn mở những nhóm trông đáng ngờ.

Đo thời gian chạy với console.time

Để kiểm tra hiệu năng nhanh, khó có gì tiện hơn console.time và console.timeEnd:

Nhãn trong time và timeEnd phải trùng nhau. Bạn có thể chạy nhiều bộ đếm giờ cùng lúc với các nhãn khác nhau. Với những việc nghiêm túc hơn câu hỏi "vòng lặp này có chậm không?", hãy chuyển sang bảng Performance trong DevTools, nơi ghi lại toàn bộ timeline và flame chart.

Assertion: chỉ in khi có gì đó sai

console.assert chỉ in ra khi điều kiện là falsy. Đây là cách kín đáo để để lại các bước kiểm tra trong code mà không làm console ngập thông điệp khi mọi thứ vẫn ổn:

Rất hợp với những bất biến luôn phải đúng. Không hợp với những thứ có thể thất bại một cách hợp lệ: với chúng, hãy ném lỗi.

Stack trace khi cần

console.trace in ra call stack hiện tại mà không ném lỗi nào. Rất tiện khi bạn cần tìm xem ai đã gọi một hàm:

Kết quả hiển thị inner → outer → (top level). Trong một ứng dụng thật, đây là cách bạn phát hiện ra trình xử lý click mình đang debug bị kích hoạt từ ba chỗ khác nhau.

Câu lệnh debugger

Cách nhanh nhất để tạm dừng JavaScript chỉ cần một từ:

function computeTotal(items) {
    const subtotal = items.reduce((s, i) => s + i.price, 0);
    debugger;
    return subtotal * 1.08;
}

Khi DevTools đang mở, debugger; hoạt động như một breakpoint: chương trình dừng ở dòng đó và bạn có đầy đủ trình gỡ lỗi, gồm các biến trong phạm vi, call stack, nút step over và step into, cùng khả năng tính giá trị biểu thức trên trạng thái hiện tại. Khi DevTools đóng, debugger; không làm gì cả.

Lần đầu dùng một trình gỡ lỗi thật thay cho console.log, bạn sẽ thấy như có siêu năng lực. Bạn thấy được mọi biến trong phạm vi mà không cần quyết định trước sẽ in gì. Bạn có thể đi qua từng câu lệnh điều kiện và xem nhánh nào chạy. Sửa một bug khó giảm từ vài phút xuống vài giây.

Hãy xóa dòng debugger trước khi commit, hoặc tốt hơn, đặt breakpoint trực tiếp trong DevTools bằng cách nhấp vào số dòng trong bảng Sources.

Những mẹo DevTools nên biết

Vài tính năng mà nhiều người bỏ lỡ suốt nhiều năm:

  • Breakpoint có điều kiện: nhấp chuột phải vào số dòng trong Sources và đặt điều kiện như user.id === 42. Breakpoint chỉ kích hoạt khi điều kiện đúng.
  • Logpoint: cũng trong menu đó, chọn "Add logpoint." Nó in ra thông điệp mà không tạm dừng và không cần sửa code.
  • $_ trong console: biểu thức vừa được tính gần nhất. Chạy một thứ gì đó, rồi dùng $_ để lấy kết quả.
  • $0: phần tử đang được chọn trong bảng Elements. $0.textContent cho bạn xem nội dung của phần tử vừa nhấp vào.
  • Sao chép thành đối tượng: nhấp chuột phải vào bất kỳ giá trị nào trong console và chọn "Store as global variable." Bạn sẽ có temp1, temp2 và tiếp tục như vậy để thử nghiệm.
  • Bảng Network → Copy as fetch: biến bất kỳ request nào thành một lời gọi fetch() để dán vào console và chỉnh sửa.

Không tính năng nào là bắt buộc. Nhưng tất cả đều giúp tiết kiệm thời gian khi đã thành phản xạ.

Dọn dẹp trước khi phát hành

Những lời gọi console.log còn sót lại thì vô hại khi phát triển nhưng gây nhiễu trên production. Vài thói quen hữu ích:

  • Dùng một quy tắc lint (no-console trong ESLint) để đánh dấu các dòng log bị bỏ quên, với ngoại lệ cho warn và error.
  • Bọc các log chi tiết trong một điều kiện: if (process.env.NODE_ENV !== "production") console.log(...).
  • Ưu tiên console.debug cho các thông điệp mức trace, vì hầu hết bundler và công cụ gom log đều lọc được nó.
  • Tốt hơn nữa: dùng một module logger nhỏ (hoặc thư viện như debug) để bật tắt từng nhóm log mà không cần sửa code.

Log không miễn phí. Mỗi lời gọi đều phải tuần tự hóa các đối số và ghi vào bộ đệm. Trong một vòng lặp chạy liên tục, một dòng log bị quên có thể làm chương trình chậm đi thấy rõ.

Tiếp theo: biểu thức chính quy

Bạn sẽ phải debug rất nhiều code xử lý chuỗi, và phần lớn code đó dùng regex, tính năng gọn nhất và cũng khó hiểu nhất của ngôn ngữ. Chương tiếp theo bắt đầu bằng một chuyến tham quan nhẹ nhàng về biểu thức chính quy trong JavaScript và các phương thức dùng chúng.

Câu hỏi thường gặp

console.log và console.dir khác nhau thế nào?

console.log in giá trị theo định dạng mặc định của trình duyệt, với phần tử DOM thì nghĩa là hiển thị dưới dạng HTML. console.dir luôn in ra dạng đối tượng JavaScript cùng các thuộc tính của nó. Đó là thứ bạn cần khi muốn xem thuộc tính của một phần tử thay vì markup của nó.

Làm sao debug JavaScript trong Chrome DevTools mà không cần console.log?

Mở bảng Sources, tìm tệp của bạn và nhấp vào số dòng để đặt breakpoint. Khi chương trình chạy tới dòng đó, nó sẽ tạm dừng để bạn xem các biến, chạy từng bước và tính giá trị biểu thức trong console. Bạn cũng có thể đặt câu lệnh debugger; ngay trong code để kích hoạt breakpoint từ mã nguồn.

Làm sao đo thời gian chạy của một đoạn code JavaScript?

Đặt đoạn code giữa console.time('label') và console.timeEnd('label') với cùng một nhãn. Console sẽ in ra thời gian đã trôi qua tính bằng mili giây. Muốn phân tích chi tiết hơn, hãy dùng bảng Performance trong DevTools để ghi lại flame chart của mọi thứ đã chạy.

console.table dùng để làm gì?

console.table hiển thị mảng và đối tượng thành một bảng có thể sắp xếp trong console, dễ đọc hơn nhiều so với một đối tượng lồng nhau in ra nguyên khối. Nó rất hợp với mảng các đối tượng: mỗi đối tượng thành một hàng, các khóa thành cột. Bạn có thể truyền đối số thứ hai để giới hạn những cột được hiển thị.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu