Các phần tử và nút HTML DOM (DOM Nodes) trong JavaScript

JavaScript HTML DOM | by Học Javascript

Trong lập trình JavaScript, HTML DOM (Document Object Model) đóng vai trò quan trọng trong việc tương tác và thao tác với nội dung của trang web. Trong đó, DOM Nodes là các thành phần cơ bản tạo nên cấu trúc của một tài liệu HTML, giúp lập trình viên có thể truy cập, thay đổi và quản lý các phần tử một cách linh hoạt.

Việc hiểu rõ các loại DOM Nodes như Element Node, Attribute Node, Text Node, Comment Node và cách thao tác với chúng không chỉ giúp tối ưu hiệu suất mà còn nâng cao khả năng kiểm soát giao diện web. Bài viết này sẽ giúp bạn tìm hiểu chi tiết về các loại DOM Nodes, cách sử dụng chúng trong JavaScript, cũng như ứng dụng thực tế trong phát triển web.

DOM Nodes trong JavaScript

Khái niệm DOM Nodes là gì?

Trong HTML DOM (Document Object Model), mọi thành phần trong tài liệu HTML đều được biểu diễn dưới dạng một Node (nút). Mỗi thẻ HTML, thuộc tính, đoạn văn bản hay thậm chí là chú thích trong mã nguồn HTML đều là một node trong cây DOM.

Cấu trúc của một tài liệu HTML được thể hiện dưới dạng cây DOM Tree, trong đó:

  • Mỗi phần tử HTML (như <div>, <p>, <h1>) là một Element Node.

  • Nội dung văn bản bên trong thẻ là một Text Node.

  • Các thuộc tính (như id, class, href) là Attribute Nodes.

  • Các chú thích HTML (<!-- comment -->) là Comment Nodes.

  • Tài liệu HTML gốc (document) là Document Node.

Ví dụ về DOM Tree của một đoạn HTML đơn giản:

<!DOCTYPE html>
<html>
<head>
    <title>DOM Nodes Example</title>
</head>
<body>
    <h1 id="title">Chào mừng đến với DOM!</h1>
    <p class="description">Đây là một đoạn văn bản.</p>
</body>
</html>

Mô hình DOM tương ứng:

Document
 ├── <html>
 │    ├── <head>
 │    │    ├── <title> → (Text Node: "DOM Nodes Example")
 │    ├── <body>
 │         ├── <h1 id="title"> → (Text Node: "Chào mừng đến với DOM!")
 │         ├── <p class="description"> → (Text Node: "Đây là một đoạn văn bản.")

Vai trò của DOM Nodes trong cấu trúc HTML DOM

DOM Nodes đóng vai trò quan trọng trong việc biểu diễn và thao tác với nội dung của trang web. Một số vai trò chính bao gồm:

1. Biểu diễn cấu trúc tài liệu HTML dưới dạng cây DOM

  • DOM Nodes giúp trình duyệt hiểu và hiển thị nội dung trang web theo dạng cây phân cấp.

  • Lập trình viên có thể dễ dàng truy cập từng phần tử, chỉnh sửa hoặc thay đổi nội dung trang web.

2. Hỗ trợ tương tác động với trang web

  • Cho phép JavaScript thao tác với nội dung HTML theo thời gian thực.

  • Ví dụ: Khi người dùng nhập dữ liệu vào form, JavaScript có thể kiểm tra giá trị và hiển thị cảnh báo nếu nhập sai.

3. Giúp thao tác với các phần tử HTML linh hoạt hơn

  • Có thể thêm/xóa/sửa phần tử hoặc nội dung mà không cần tải lại trang.

  • Ví dụ: Tạo danh sách sản phẩm động mà không cần chỉnh sửa trực tiếp HTML.

4. Hỗ trợ xử lý sự kiện trong trang web

  • Cho phép bắt sự kiện từ người dùng như click, nhập liệu, di chuột,…

  • Ví dụ: Hiển thị popup khi người dùng nhấn nút.

5. Tạo điều kiện thuận lợi cho các hiệu ứng động

  • Sử dụng JavaScript kết hợp với CSS để tạo hiệu ứng chuyển động, thay đổi giao diện.

  • Ví dụ: Ẩn/hiện menu khi nhấn vào biểu tượng.

Các loại DOM Nodes trong JavaScript

DOM (Document Object Model) tổ chức tài liệu HTML dưới dạng một cây DOM Tree, trong đó mỗi thành phần trong HTML được biểu diễn dưới dạng một Node (nút). Các loại DOM Nodes quan trọng trong JavaScript bao gồm:

Element Node (Nút phần tử)

  • Element Node là đại diện cho các thẻ HTML như <div>, <p>, <h1>, <span>, <a>,…

  • Mỗi phần tử HTML là một Element Node, có thể chứa các Attribute Nodes (thuộc tính), Text Nodes (nội dung văn bản) hoặc các Element Nodes khác (phần tử con).

Ví dụ về Element Node trong HTML:

<h1 id="title" class="header">Chào mừng đến với DOM!</h1>
  • <h1> là một Element Node.

  • Thuộc tính id="title"class="header" thuộc Attribute Node.

  • Nội dung "Chào mừng đến với DOM!" là một Text Node bên trong <h1>.

Cách truy xuất phần tử trong DOM

JavaScript cung cấp nhiều cách để lấy một Element Node trong DOM:

Sử dụng getElementById() – Lấy phần tử theo id

let title = document.getElementById("title"); 
console.log(title); // Trả về phần tử <h1 id="title">...</h1>

Sử dụng getElementsByClassName() – Lấy danh sách các phần tử theo class

let headers = document.getElementsByClassName("header"); 
console.log(headers[0]); // Trả về phần tử đầu tiên có class "header"
Sử dụng getElementsByTagName() – Lấy danh sách các phần tử theo tên thẻ
let paragraphs = document.getElementsByTagName("p"); 
console.log(paragraphs[0]); // Trả về phần tử <p> đầu tiên
Sử dụng querySelector() – Lấy phần tử đầu tiên khớp với selector
let title = document.querySelector("h1"); 
console.log(title); // Trả về phần tử <h1> đầu tiên
Sử dụng querySelectorAll() – Lấy danh sách tất cả các phần tử khớp với selector
let allHeaders = document.querySelectorAll(".header"); 
console.log(allHeaders); // Trả về danh sách các phần tử có class "header"

Thao tác với Element Node

Thay đổi nội dung của phần tử

title.textContent = "Xin chào DOM!"; // Thay đổi nội dung của thẻ <h1>
Thêm hoặc xóa class
title.classList.add("highlight");  // Thêm class "highlight"
title.classList.remove("header"); // Xóa class "header"
Chỉnh sửa CSS bằng JavaScript
title.style.color = "blue";  // Đổi màu chữ thành xanh
title.style.fontSize = "24px"; // Thay đổi kích thước chữ

Attribute Node (Nút thuộc tính)

Attribute Node là đại diện cho thuộc tính của một phần tử HTML, như id, class, src, href, alt,…

  • Các thuộc tính không được coi là node riêng biệt trong DOM hiện đại, nhưng chúng có thể được truy xuất và thay đổi thông qua Element Node.

Ví dụ về Attribute Node trong HTML:

<img id="logo" src="logo.png" alt="Logo trang web">
  • Thuộc tính src="logo.png", alt="Logo trang web" thuộc Attribute Node của thẻ <img>.

Cách truy xuất và thay đổi giá trị thuộc tính

Lấy giá trị thuộc tính với getAttribute()

let logo = document.getElementById("logo");
console.log(logo.getAttribute("src")); // Kết quả: "logo.png"

Thay đổi giá trị thuộc tính với setAttribute()

logo.setAttribute("src", "new-logo.png"); // Thay đổi hình ảnh

Xóa thuộc tính với removeAttribute()

logo.removeAttribute("alt"); // Xóa thuộc tính alt

Truy cập nhanh thuộc tính phổ biến

logo.src = "new-logo.png";  // Cách nhanh hơn để thay đổi src
logo.alt = "Hình ảnh mới";  // Thay đổi alt

Text Node (Nút văn bản)

  • Text Node chứa nội dung văn bản bên trong một phần tử HTML.

  • Không thể chứa thẻ HTML bên trong Text Node, chỉ là đoạn văn bản thuần.

Ví dụ về Text Node trong HTML:

<p id="desc">Đây là một đoạn văn bản.</p>
  • "Đây là một đoạn văn bản." là một Text Node của thẻ <p>.

Cách truy cập và thay đổi nội dung của Text Node

Sử dụng nodeValue để lấy hoặc thay đổi nội dung

let desc = document.getElementById("desc").firstChild;
console.log(desc.nodeValue); // Kết quả: "Đây là một đoạn văn bản."
desc.nodeValue = "Văn bản mới"; // Thay đổi nội dung

Sử dụng textContent để thay đổi nội dung an toàn hơn

desc.textContent = "Văn bản cập nhật!"; 

Phân biệt innerTexttextContent

  • innerText: Bỏ qua các thẻ ẩn, chỉ lấy nội dung hiển thị.

  • textContent: Lấy toàn bộ nội dung văn bản, kể cả nội dung ẩn.

Comment Node (Nút chú thích)

  • Comment Node đại diện cho các đoạn chú thích trong HTML.

  • Không ảnh hưởng đến hiển thị trang web nhưng có thể được truy cập bằng JavaScript.

Ví dụ về Comment Node trong HTML:

<!-- Đây là một chú thích -->

Cách truy xuất và thao tác với Comment Node

let comment = document.createComment("Chú thích mới");
document.body.appendChild(comment); // Thêm một comment mới vào cuối <body>

Document Node (Nút tài liệu)

  • Document Node là node gốc đại diện cho toàn bộ tài liệu HTML.

  • Thường được truy cập thông qua document.

Truy cập các phần tử trong tài liệu thông qua document

Truy xuất thông tin về tài liệu

console.log(document.title);  // Lấy tiêu đề trang
console.log(document.URL);    // Lấy URL trang

Truy cập phần tử body hoặc head

console.log(document.body);  // Trả về thẻ <body>
console.log(document.head);  // Trả về thẻ <head>

Thêm nội dung mới vào body

let newHeading = document.createElement("h2");
newHeading.textContent = "Đây là tiêu đề mới!";
document.body.appendChild(newHeading);

Cách thao tác với DOM Nodes trong JavaScript

DOM Nodes không chỉ giúp lập trình viên truy cập và lấy dữ liệu từ trang web mà còn hỗ trợ thêm mới, xóa hoặc sao chép các phần tử một cách linh hoạt.

Dưới đây là các thao tác phổ biến khi làm việc với DOM Nodes trong JavaScript.

Thêm mới một node vào DOM

JavaScript cung cấp nhiều phương thức để tạo mới và chèn các Node vào DOM.

createElement() – Tạo một Element Node

Phương thức này tạo một phần tử HTML mới nhưng chưa gắn vào cây DOM.

Ví dụ:

let newDiv = document.createElement("div"); // Tạo phần tử <div>
newDiv.textContent = "Đây là một thẻ div mới"; // Thêm nội dung
console.log(newDiv); // Hiển thị phần tử vừa tạo

Lưu ý:

  • createElement() chỉ tạo phần tử, muốn hiển thị trên trang web, cần thêm vào DOM.

createTextNode() – Tạo một Text Node

Dùng để tạo nội dung văn bản mà không chứa thẻ HTML.

Ví dụ:

let textNode = document.createTextNode("Đây là nội dung văn bản!");
console.log(textNode); // Trả về Text Node

Khác biệt so với textContent?

  • textContent trực tiếp thay đổi nội dung phần tử.

  • createTextNode() tạo một Node riêng biệt để chèn vào DOM.

appendChild() – Thêm node vào cuối danh sách con

Sau khi tạo Element Node hoặc Text Node, ta cần thêm vào một phần tử khác để nó hiển thị trên trang.

Ví dụ:

let newParagraph = document.createElement("p"); // Tạo thẻ <p>
newParagraph.textContent = "Đây là một đoạn văn mới"; // Thêm nội dung

document.body.appendChild(newParagraph); // Thêm vào cuối <body>

insertBefore() – Chèn node vào trước một node khác

Nếu muốn chèn vào một vị trí cụ thể, ta sử dụng insertBefore().

Ví dụ

let parent = document.getElementById("container"); // Lấy phần tử cha
let newItem = document.createElement("li"); 
newItem.textContent = "Mục mới"; // Tạo mục mới

let referenceNode = parent.firstChild; // Lấy phần tử con đầu tiên
parent.insertBefore(newItem, referenceNode); // Chèn vào trước phần tử con đầu tiên

Lưu ý:

  • Nếu referenceNodenull, insertBefore() hoạt động giống appendChild().

Xóa một node khỏi DOM

removeChild() – Xóa một node con

Dùng để xóa một phần tử con cụ thể khỏi phần tử cha.

Ví dụ:

let list = document.getElementById("myList"); // Lấy danh sách
let firstItem = list.firstElementChild; // Lấy mục đầu tiên

if (firstItem) {
    list.removeChild(firstItem); // Xóa mục đầu tiên
}

Lưu ý:

  • Cần đảm bảo phần tử tồn tại trước khi xóa (if (firstItem)).

  • removeChild() yêu cầu truyền vào phần tử con cần xóa.

replaceChild() – Thay thế một node

Dùng để thay thế một phần tử con bằng một phần tử mới.

Ví dụ:

let parent = document.getElementById("content"); // Lấy phần tử cha
let oldItem = parent.firstElementChild; // Lấy phần tử đầu tiên

let newItem = document.createElement("p"); // Tạo phần tử mới
newItem.textContent = "Đây là phần tử mới!";

if (oldItem) {
    parent.replaceChild(newItem, oldItem); // Thay thế phần tử cũ
}

Lưu ý:

  • replaceChild(newNode, oldNode) yêu cầu cả phần tử mới và phần tử cũ phải tồn tại.

  • oldNode sẽ bị xóa khỏi DOM sau khi thay thế.

Clone một node trong DOM

cloneNode(true/false) – Nhân bản một node

Dùng để tạo bản sao của một phần tử hiện có.

Cú pháp:

element.cloneNode(deep);
  • deep = false: Chỉ sao chép chính phần tử đó, không sao chép nội dung con.

  • deep = true: Sao chép cả phần tử và toàn bộ nội dung con.

Ví dụ 1: Nhân bản một phần tử nhưng không có nội dung con (false)

let originalDiv = document.getElementById("box"); 
let clonedDiv = originalDiv.cloneNode(false); // Chỉ sao chép chính nó, không có nội dung con

document.body.appendChild(clonedDiv); // Thêm bản sao vào trang

Kết quả:

  • Nếu #box có nội dung bên trong, bản sao sẽ chỉ có thẻ rỗng mà không có nội dung.

Ví dụ 2: Nhân bản một phần tử và toàn bộ nội dung (true)

let originalDiv = document.getElementById("box"); 
let clonedDiv = originalDiv.cloneNode(true); // Sao chép toàn bộ

document.body.appendChild(clonedDiv); // Thêm bản sao vào trang

Kết quả:

  • Bản sao sẽ chứa toàn bộ nội dung, kể cả các phần tử con

Phân biệt NodeList và HTMLCollection trong JavaScript

Khi làm việc với DOM Nodes, ta thường gặp hai loại danh sách phần tử phổ biến là NodeList và HTMLCollection. Mặc dù chúng đều chứa tập hợp các phần tử DOM, nhưng có một số điểm khác biệt quan trọng cần lưu ý.

Sự khác nhau giữa NodeList và HTMLCollection

Thuộc tính NodeList HTMLCollection
Định nghĩa Một danh sách chứa các node trong DOM (có thể là phần tử, văn bản, hoặc comment). Một danh sách chứa các phần tử HTML trong DOM.
Cách truy xuất phần tử querySelectorAll(), childNodes. getElementsByTagName(), getElementsByClassName(), children.
Dạng danh sách Có thể tĩnh (static) hoặc sống (live). Luôn là danh sách sống (live).
Duyệt qua danh sách Hỗ trợ forEach() (với danh sách tĩnh), for loop. Không hỗ trợ forEach(), chỉ có thể duyệt bằng for loop.
Cập nhật theo DOM - Tĩnh: querySelectorAll() không cập nhật khi DOM thay đổi.
- Sống: childNodes cập nhật khi DOM thay đổi.
Luôn cập nhật khi DOM thay đổi.

Ví dụ minh họa HTMLCollection

NodeList từ querySelectorAll() (tĩnh)

let nodeList = document.querySelectorAll("p"); // Lấy tất cả thẻ <p>
console.log(nodeList); // NodeList (tĩnh)
  • Nếu thêm một thẻ <p> mới vào DOM sau khi gọi querySelectorAll(), danh sách không thay đổi.


HTMLCollection từ getElementsByTagName() (sống)

let htmlCollection = document.getElementsByTagName("p"); // Lấy tất cả thẻ <p>
console.log(htmlCollection); // HTMLCollection (luôn cập nhật)
  • Nếu thêm một thẻ <p> mới, danh sách cập nhật ngay lập tức.

Cách duyệt qua danh sách nodes

Dùng forEach() (chỉ áp dụng cho NodeList)

let nodeList = document.querySelectorAll("p");

nodeList.forEach((node) => {
    console.log(node.textContent);
});

Lưu ý:

  • forEach() chỉ hoạt động với NodeList tĩnh (querySelectorAll()), không áp dụng cho HTMLCollection.

Dùng vòng lặp for (dùng được cho cả hai)

let htmlCollection = document.getElementsByClassName("item");

for (let i = 0; i < htmlCollection.length; i++) {
    console.log(htmlCollection[i].textContent);
}

Ưu điểm:

  • for loop có thể dùng cho cả NodeList và HTMLCollection.

Lưu ý khi làm việc với DOM Nodes trong JavaScript

Làm việc với DOM hiệu quả giúp tối ưu hiệu suất và tránh lỗi khi thao tác với giao diện trang web.

Hạn chế thao tác trực tiếp lên DOM để tránh ảnh hưởng hiệu suất

Mỗi khi thay đổi DOM, trình duyệt phải render lại toàn bộ trang web, gây chậm hiệu suất.

Ví dụ (Không tối ưu - Chỉnh sửa DOM nhiều lần)

let list = document.getElementById("list");
for (let i = 0; i < 1000; i++) {
    let item = document.createElement("li");
    item.textContent = "Mục " + (i + 1);
    list.appendChild(item); // Mỗi lần lặp lại, cập nhật DOM
}

Giải pháp: Sử dụng Document Fragment để tối ưu.

Cách tối ưu (Dùng DocumentFragment)

let list = document.getElementById("list");
let fragment = document.createDocumentFragment(); // Tạo một vùng chứa tạm

for (let i = 0; i < 1000; i++) {
    let item = document.createElement("li");
    item.textContent = "Mục " + (i + 1);
    fragment.appendChild(item); // Thêm vào fragment
}

list.appendChild(fragment); // Chỉ cập nhật DOM một lần

Hiệu quả: Giảm số lần cập nhật DOM → Tăng tốc độ hiển thị.

Phân biệt giữa innerHTML, innerText và textContent

Phương thức Mô tả Khi nào nên dùng?
innerHTML Trả về hoặc thiết lập nội dung HTML của phần tử. Khi cần cập nhật nội dung có thẻ HTML.
innerText Trả về nội dung văn bản hiển thị trên trang, bỏ qua các thẻ ẩn. Khi chỉ cần văn bản hiển thị với người dùng.
textContent Trả về toàn bộ nội dung văn bản, kể cả thẻ ẩn. Khi cần lấy nội dung mà không bị ảnh hưởng bởi CSS.

Ví dụ minh họa

<div id="demo">
    <p style="display: none;">Đoạn văn bị ẩn</p>
    <b>Chữ in đậm</b>
</div>
let demo = document.getElementById("demo");

console.log(demo.innerHTML);   // "<p style="display: none;">Đoạn văn bị ẩn</p><b>Chữ in đậm</b>"
console.log(demo.innerText);   // "Chữ in đậm"
console.log(demo.textContent); // "Đoạn văn bị ẩnChữ in đậm"

Lưu ý:

  • innerHTML có thể thêm mã HTML, nhưng nguy cơ bảo mật nếu nhập từ người dùng.

  • innerText bỏ qua nội dung ẩn.

  • textContent lấy toàn bộ văn bản, kể cả văn bản bị ẩn.

Tránh sử dụng document.write()

document.write() ghi đè nội dung trang và có thể gây lỗi.

Không nên dùng document.write()

document.write("<h1>Hello World</h1>");

Hậu quả:

  • Nếu gọi sau khi trang đã tải, nó sẽ xóa toàn bộ nội dung trang.

  • Không an toàn khi xử lý dữ liệu nhập từ người dùng.

Cách thay thế: Dùng innerHTML hoặc textContent

document.getElementById("content").innerHTML = "<h1>Hello World</h1>";

Ưu điểm: Không làm mất nội dung trang.

Ứng dụng thực tế của DOM Nodes trong JavaScript

DOM Nodes không chỉ giúp truy xuất và thay đổi nội dung trên trang web mà còn là nền tảng quan trọng để xây dựng các ứng dụng tương tác động. Dưới đây là một số ứng dụng thực tế khi sử dụng DOM Nodes trong JavaScript.

Tạo danh sách sản phẩm động trên trang web

Trong các trang thương mại điện tử, danh sách sản phẩm thường được tạo động dựa trên dữ liệu từ cơ sở dữ liệu hoặc API.

Ví dụ: Tạo danh sách sản phẩm từ mảng dữ liệu

<ul id="product-list"></ul>
let products = [
    { name: "Laptop", price: "$1000" },
    { name: "Điện thoại", price: "$500" },
    { name: "Tai nghe", price: "$100" }
];

let productList = document.getElementById("product-list");

products.forEach(product => {
    let li = document.createElement("li"); // Tạo Element Node <li>
    li.textContent = `${product.name} - ${product.price}`;
    productList.appendChild(li); // Thêm vào danh sách
});

Kết quả: Danh sách sản phẩm hiển thị tự động mà không cần viết trực tiếp trong HTML.

Xử lý sự kiện dựa trên các phần tử trong DOM

DOM Nodes giúp dễ dàng thêm hoặc gỡ bỏ sự kiện cho các phần tử trên trang web.

Ví dụ: Thêm sự kiện click vào danh sách sản phẩm

<ul id="product-list">
    <li>Laptop - $1000</li>
    <li>Điện thoại - $500</li>
    <li>Tai nghe - $100</li>
</ul>
document.querySelectorAll("#product-list li").forEach(item => {
    item.addEventListener("click", () => {
        alert(`Bạn đã chọn: ${item.textContent}`);
    });
});

Kết quả: Khi người dùng click vào một sản phẩm, thông báo sẽ hiển thị.

Xây dựng ứng dụng tương tác như menu động, form tự động cập nhật

Ví dụ: Tạo menu động thay đổi theo thao tác người dùng

<button id="toggle-menu">Bật/tắt menu</button>
<ul id="menu" style="display: none;">
    <li>Trang chủ</li>
    <li>Sản phẩm</li>
    <li>Liên hệ</li>
</ul>
document.getElementById("toggle-menu").addEventListener("click", function() {
    let menu = document.getElementById("menu");
    menu.style.display = menu.style.display === "none" ? "block" : "none";
});

Kết quả: Khi nhấn nút, menu sẽ bật hoặc tắt mà không cần tải lại trang.

Ví dụ: Form tự động cập nhật tổng giá trị đơn hàng

<label>Số lượng:</label>
<input type="number" id="quantity" value="1">
<p>Tổng giá: <span id="total">$1000</span></p>
document.getElementById("quantity").addEventListener("input", function() {
    let price = 1000; // Giá của sản phẩm
    let quantity = this.value;
    document.getElementById("total").textContent = `$${price * quantity}`;
});

Kết quả: Khi người dùng thay đổi số lượng, tổng giá sẽ cập nhật ngay lập tức.

Kết bài

DOM Nodes đóng vai trò quan trọng trong việc xây dựng và quản lý nội dung trang web. Bằng cách hiểu rõ về các loại Node như Element Node, Text Node, Attribute Node, Comment Node và cách thao tác với chúng, lập trình viên có thể dễ dàng tạo, chỉnh sửa, xóa, hoặc cập nhật nội dung một cách linh hoạt.

Bên cạnh đó, việc sử dụng DOM Navigation giúp điều hướng nhanh chóng giữa các phần tử trong cây DOM, từ đó cải thiện hiệu suất và tối ưu hóa trải nghiệm người dùng. Các ứng dụng thực tế như tạo danh sách sản phẩm động, xử lý sự kiện, xây dựng menu tương tác, và form tự động cập nhật cho thấy sự quan trọng của DOM Nodes trong phát triển web.

Tuy nhiên, để tối ưu hiệu suất, cần hạn chế thao tác trực tiếp lên DOM và sử dụng các phương pháp hiệu quả như NodeList, DocumentFragment hoặc kỹ thuật cập nhật hàng loạt thay vì cập nhật từng phần tử riêng lẻ.

Bài viết liên quan