Khám Phá Mô Hình Đối Tượng Tài Liệu (DOM) trong JavaScript
Giới thiệu
Trong hành trình tìm hiểu các khối xây dựng của Kỹ Thuật Frontend, tôi đã tham gia một khóa học trên Frontend Masters có tiêu đề Vanilla JavaScript: You might not need a Framework của Maximiliano Firtman. Đây là một khóa học thú vị mà bạn nên tham khảo.
Bài viết này nhằm chia sẻ những gì tôi đã học từ khóa học và làm sâu sắc thêm kiến thức về Vanilla JS. Một trong những khái niệm chính mà khóa học khám phá sâu là Mô Hình Đối Tượng Tài Liệu (DOM), là nền tảng cho cách JavaScript tương tác với các trang web.
Việc hiểu rõ cách thức hoạt động của DOM là rất quan trọng đối với các Kỹ Sư Frontend, vì điều này giúp đưa ra những quyết định tốt hơn về hiệu suất khi làm việc với JavaScript, cho dù bạn đang sử dụng Vanilla JS hay các framework như React.
Định nghĩa về DOM
Dưới đây là những định nghĩa về DOM mà tôi cảm thấy ấn tượng nhất từ khóa học:
- DOM là một đại diện trong bộ nhớ của HTML của bạn.
- DOM là một cấu trúc mà các trình duyệt tạo ra trong bộ nhớ khi hiển thị các trang.
- DOM là một cầu nối kết nối các trang web với JavaScript thông qua cấu trúc tài liệu.
Để tương tác với DOM, trình duyệt cung cấp các Giao Diện Lập Trình Ứng Dụng (API) gọi là DOM API. DOM API là một API của trình duyệt được công khai cho các nhà phát triển để thao tác với DOM từ một ngôn ngữ kịch bản như JavaScript.
Về cơ bản, DOM là một cấu trúc trong bộ nhớ trong khi DOM API cung cấp các phương thức để thay đổi cấu trúc đó. API này có sẵn trên nhiều đối tượng như:
- Đối tượng toàn cục Window
- Đối tượng Document (đại diện cho DOM hiện tại)
- Mỗi phần tử HTML trong một ứng dụng web
Làm việc với DOM
Như đã đề cập trước đó, các API của DOM cho phép chúng ta tương tác và thao tác với DOM. Dưới đây là một số cách mà chúng ta có thể thực hiện điều đó:
1. Chọn phần tử từ DOM
Chúng ta có thể chọn các phần tử từ DOM bằng nhiều phương pháp khác nhau:
- Theo ID: Để lấy một phần tử HTML theo thuộc tính id của nó trong JavaScript, chúng ta sử dụng phương thức
document.getElementById()
. Phương thức này trả về một phần tử HTML duy nhất (hoặcnull
nếu không tìm thấy phần tử nào). Vì id là duy nhất, nó chỉ trả về phần tử đầu tiên mà nó gặp với id đó.
html
<!-- HTML -->
<div id="header">Chào mừng đến với trang của tôi</div>
<p id="intro">Đây là đoạn giới thiệu</p>
javascript
// JavaScript
const headerElement = document.getElementById('header');
const introElement = document.getElementById('intro');
console.log(headerElement.textContent); // "Chào mừng đến với trang của tôi"
console.log(introElement.textContent); // "Đây là đoạn giới thiệu"
// Nếu ID không tồn tại
const nonExistent = document.getElementById('notFound');
console.log(nonExistent); // null
- Theo Classname: Chúng ta cũng có thể lấy một phần tử HTML theo classname, sử dụng phương thức
document.getElementsByClassName()
. Phương thức này trả về một HTML Collection sống - một tập hợp sống tự động cập nhật khi DOM thay đổi.
html
<!-- HTML -->
<div class="card">Thẻ 1</div>
<div class="card">Thẻ 2</div>
<p class="card">Thẻ 3</p>
javascript
// JavaScript
const cardElements = document.getElementsByClassName('card');
console.log(cardElements.length); // 3
// Truy cập từng phần tử
console.log(cardElements[0].textContent); // "Thẻ 1"
console.log(cardElements[1].textContent); // "Thẻ 2"
// Tập hợp sống - tự động cập nhật
const newCard = document.createElement('div');
newCard.className = 'card';
newCard.textContent = 'Thẻ 4';
document.body.appendChild(newCard);
console.log(cardElements.length); // Bây giờ là 4 (tự động cập nhật!)
Lưu ý: Các HTMLCollection sống không có toàn bộ giao diện mảng hiện đại như filter, map, reduce hoặc forEach. Các hàm mảng hiện đại có thể được thêm vào một HTMLCollection sống bằng cách tạo một mảng từ nó bằng phương thức Array.from().
- Theo Tên: Các phần tử cũng có thể được lấy bằng thuộc tính name của chúng với phương thức
document.getElementsByName()
. Phương thức này trả về một Static NodeList. Vì thuộc tính name không bắt buộc phải duy nhất (khác với id), phương thức này thường trả về một tập hợp các phần tử, ngay cả khi chỉ có một phần tử với tên đó.
html
<form>
<input type="radio" name="gender" value="male"> Nam
<input type="radio" name="gender" value="female"> Nữ
<input type="radio" name="gender" value="other"> Khác
</form>
javascript
// JavaScript
const genderInputs = document.getElementsByName('gender');
console.log(genderInputs.length); // 3
// Lặp qua tập hợp
genderInputs.forEach((input, index) => {
console.log(`Input ${index + 1}: ${input.value}`);
});
// Kết quả:
// Input 1: male
// Input 2: female
// Input 3: other
// Kiểm tra phần nào được chọn
const selectedGender = Array.from(genderInputs).find(input => input.checked);
console.log(selectedGender?.value); // Giá trị của nút radio đã chọn
- Theo CSS Selectors: Các phần tử cũng có thể được lấy bằng cách sử dụng các bộ chọn CSS thông qua phương thức
document.querySelector(selector)
. Phương thức này trả về phần tử đầu tiên trong tài liệu khớp với bộ chọn CSS đã chỉ định. Nếu không tìm thấy phần tử nào, nó trả về null.
html
<!-- HTML -->
<div class="container">
<h1 id="main-title">Chào mừng</h1>
<p class="intro">Đoạn văn đầu tiên</p>
<p class="intro">Đoạn văn thứ hai</p>
<button type="submit" data-action="save">Lưu</button>
<input type="email" placeholder="Nhập email">
</div>
javascript
// Chọn theo ID (giống như getElementById)
const title = document.querySelector('#main-title');
console.log(title.textContent); // "Chào mừng"
// Chọn theo class (trả về phần tử khớp đầu tiên)
const firstIntro = document.querySelector('.intro');
console.log(firstIntro.textContent); // "Đoạn văn đầu tiên"
// Chọn theo thẻ phần tử
const firstParagraph = document.querySelector('p');
console.log(firstParagraph.textContent); // "Đoạn văn đầu tiên"
// Chọn theo thuộc tính
const submitButton = document.querySelector('[type="submit"]');
const saveButton = document.querySelector('[data-action="save"]');
console.log(submitButton.textContent); // "Lưu"
// Chọn theo bộ chọn CSS phức tạp
const emailInput = document.querySelector('input[type="email"]');
console.log(emailInput.placeholder); // "Nhập email"
// Bộ chọn con
const containerTitle = document.querySelector('.container h1');
console.log(containerTitle.textContent); // "Chào mừng"
// Nếu bộ chọn không khớp với bất kỳ phần tử nào
const nonExistent = document.querySelector('.not-found');
console.log(nonExistent); // null
Thực hành tốt nhất khi làm việc với DOM
- Tối ưu hóa hiệu suất: Hạn chế việc thao tác DOM trực tiếp, thay vào đó, hãy sử dụng các phương thức như
documentFragment
để tạo và thêm nhiều phần tử cùng một lúc. - Sử dụng CSS Classes thay vì Style: Thay vì thay đổi thuộc tính style trực tiếp, hãy thêm hoặc xóa các lớp CSS để duy trì sự tách biệt giữa nội dung và kiểu dáng.
- Sử dụng Event Delegation: Khi làm việc với các sự kiện, hãy cân nhắc việc gán sự kiện cho phần tử cha để giảm thiểu số lượng gán sự kiện, giúp cải thiện hiệu suất.
Những cạm bẫy phổ biến
- Quên kiểm tra null: Khi lấy phần tử bằng
getElementById()
hoặcquerySelector()
, hãy luôn kiểm tra xem phần tử có tồn tại không trước khi thao tác với nó. - Bỏ qua sự khác biệt giữa HTMLCollection và NodeList: Hiểu sự khác biệt giữa các kiểu dữ liệu này để tránh nhầm lẫn khi làm việc với các tập hợp phần tử.
Mẹo tối ưu hiệu suất
- Tránh thao tác DOM nhiều lần: Thay vì thay đổi DOM nhiều lần, hãy tạo một mảng các phần tử cần thay đổi và thực hiện thao tác một lần.
- Sử dụng
requestAnimationFrame
: Nếu bạn thực hiện nhiều thay đổi DOM trong một khung hình, hãy sử dụngrequestAnimationFrame
để đảm bảo hiệu suất mượt mà.
Giải quyết sự cố
- Phần tử không xuất hiện: Nếu bạn không thấy phần tử hiển thị, hãy kiểm tra xem nó có bị ẩn bởi CSS không hoặc có bị xóa khỏi DOM không.
- Sự kiện không hoạt động: Đảm bảo rằng bạn đã gán sự kiện cho đúng phần tử và rằng phần tử đó có thể tương tác được.
Kết luận
Hiểu cách chọn các phần tử DOM là nền tảng của phát triển web với JavaScript. Dù bạn đang sử dụng getElementById()
cho các phần tử duy nhất, getElementsByClassName()
cho các tập hợp, getElementsByName()
cho các phần tử biểu mẫu, hay querySelector()
để chọn linh hoạt dựa trên CSS, việc thành thạo các phương pháp này cho bạn công cụ để tương tác với bất kỳ trang web nào.
Trong phần tiếp theo của loạt bài này, chúng ta sẽ khám phá cách thao tác với các phần tử đã chọn - thay đổi nội dung, thuộc tính, kiểu dáng và cấu trúc của chúng. Chúng ta cũng sẽ tìm hiểu cách tạo các phần tử mới và xử lý sự kiện để xây dựng các ứng dụng web tương tác thực sự. DOM có thể có vẻ phức tạp lúc đầu, nhưng một khi bạn hiểu những nguyên tắc lựa chọn cơ bản, bạn sẽ trên đường trở thành một chuyên gia trong JavaScript thuần.
Hãy theo dõi phần 2, nơi chúng ta sẽ khám phá thêm về thao tác DOM.
Bạn có thể liên hệ với tôi qua Twitter.