DOM Navigation với HTML DOM trong JavaScript

JavaScript HTML DOM | by Học Javascript

Trong lập trình web, Document Object Model (DOM) đóng vai trò như một cầu nối giữa JavaScript và nội dung của trang web. DOM cho phép lập trình viên truy cập, thao tác và thay đổi các phần tử HTML một cách linh hoạt. Để làm được điều này, DOM Navigation (Điều hướng DOM) cung cấp các phương pháp giúp di chuyển giữa các phần tử trong cây DOM, từ phần tử cha, phần tử con đến các phần tử anh chị em. Việc nắm vững DOM Navigation không chỉ giúp cải thiện hiệu suất thao tác với trang web mà còn là nền tảng quan trọng để xây dựng các ứng dụng web tương tác. Trong bài viết này, mình sẽ cùng tìm hiểu về các phương pháp điều hướng trong DOM và cách ứng dụng chúng trong thực tế.

Giới thiệu về DOM và DOM Navigation trong JavaScript

DOM (Document Object Model) là gì?

DOM (Document Object Model) là một mô hình lập trình đại diện cho cấu trúc của một tài liệu HTML hoặc XML dưới dạng một cây phân cấp. Khi trình duyệt tải một trang web, nó sẽ tạo ra một phiên bản DOM của trang, trong đó mỗi phần tử HTML được coi là một "node" (nút) trong cây DOM.

DOM cho phép JavaScript truy cập và thao tác các phần tử HTML bằng cách thay đổi nội dung, thuộc tính hoặc kiểu dáng của chúng. Mỗi phần tử trong trang web như <div>, <p>, <h1>, <a> đều là một node trong DOM và có thể được truy xuất hoặc chỉnh sửa bằng JavaScript.

Ví dụ về một cây DOM đơn giản của một trang HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Trang web của tôi</title>
</head>
<body>
    <h1>Chào mừng đến với trang web!</h1>
    <p>Đây là một đoạn văn.</p>
</body>
</html>

Cấu trúc cây DOM tương ứng với trang HTML trên:

Document
│
├── html
│   ├── head
│   │   └── title (Trang web của tôi)
│   ├── body
│       ├── h1 (Chào mừng đến với trang web!)
│       ├── p (Đây là một đoạn văn.)

Như vậy, mỗi phần tử HTML là một node trong cây DOM và có thể được JavaScript truy cập và thao tác thông qua DOM API.

DOM Navigation là gì?

DOM Navigation (Điều hướng DOM) là quá trình di chuyển giữa các phần tử trong cây DOM bằng cách sử dụng các thuộc tính JavaScript để truy xuất phần tử cha (parentNode), phần tử con (childNodes, children), hoặc các phần tử anh chị em (previousSibling, nextSibling).

DOM Navigation cho phép lập trình viên dễ dàng truy cập vào bất kỳ phần tử nào trong cây DOM và thực hiện các thay đổi động trên trang web mà không cần phải viết lại toàn bộ HTML.

Ví dụ: Nếu chúng ta có đoạn HTML sau:

<ul id="menu">
    <li>Trang chủ</li>
    <li>Dịch vụ</li>
    <li>Liên hệ</li>
</ul>

Ta có thể sử dụng JavaScript để điều hướng giữa các phần tử:

let menu = document.getElementById("menu"); // Lấy phần tử <ul>
console.log(menu.children); // Lấy danh sách các phần tử <li>
console.log(menu.firstElementChild.textContent); // Lấy nội dung của phần tử đầu tiên ("Trang chủ")
console.log(menu.lastElementChild.textContent); // Lấy nội dung của phần tử cuối cùng ("Liên hệ")

Tầm quan trọng của DOM Navigation trong JavaScript

DOM Navigation đóng vai trò quan trọng trong việc phát triển các ứng dụng web động vì nó giúp:

Tương tác với các phần tử HTML một cách linh hoạt

  • Thay đổi nội dung trang web mà không cần tải lại.

  • Dễ dàng thêm, xóa hoặc chỉnh sửa các phần tử HTML.

Cải thiện trải nghiệm người dùng (UX/UI)

  • Hiển thị/ẩn nội dung dựa trên hành động của người dùng.

  • Cập nhật dữ liệu theo thời gian thực mà không cần làm mới trang.

Hỗ trợ xử lý sự kiện hiệu quả hơn

  • Truy cập và gán sự kiện cho các phần tử một cách có tổ chức.

  • Điều hướng giữa các phần tử liên quan trong cùng một nhóm (ví dụ: di chuyển giữa các mục trong danh sách).

Tối ưu hiệu suất của ứng dụng web

  • Giảm số lần truy vấn trực tiếp vào DOM giúp tăng tốc độ xử lý.

  • Giảm việc thao tác không cần thiết trên DOM để tránh ảnh hưởng đến hiệu suất trình duyệt.

Ví dụ về việc thay đổi nội dung động bằng DOM Navigation:

<button id="changeText">Bấm để thay đổi</button>
<p id="message">Chào bạn!</p>

<script>
    document.getElementById("changeText").addEventListener("click", function() {
        let message = document.getElementById("message");
        message.textContent = "Nội dung đã được thay đổi!";
    });
</script>

Trong ví dụ trên, DOM Navigation giúp chúng ta tìm đến phần tử <p> và thay đổi nội dung của nó khi người dùng bấm vào nút.

Các phương pháp điều hướng trong DOM trong JavaScript

Điều hướng theo quan hệ cha - con

Cây DOM có cấu trúc phân cấp, trong đó các phần tử có mối quan hệ cha - con. Dưới đây là các phương pháp giúp truy xuất phần tử cha và con trong DOM.

parentNode – Lấy phần tử cha

  • Thuộc tính parentNode dùng để truy xuất phần tử cha (parent) của một phần tử con trong DOM.

  • Nếu phần tử không có cha (ví dụ: document), parentNode sẽ trả về null.

Ví dụ:

<div id="parent">
    <p id="child">Nội dung đoạn văn</p>
</div>
<script>
    let child = document.getElementById("child");
    console.log(child.parentNode); // Trả về <div id="parent">
</script>

childNodes – Lấy tất cả các node con (bao gồm cả text nodes)

  • Thuộc tính childNodes trả về danh sách tất cả các node con của phần tử, bao gồm cả text nodes (các khoảng trắng, xuống dòng).

  • Đối tượng trả về là một NodeList (giống mảng nhưng không phải mảng thật sự).

Ví dụ:

<ul id="menu">
    <li>Trang chủ</li>
    <li>Dịch vụ</li>
</ul>
<script>
    let menu = document.getElementById("menu");
    console.log(menu.childNodes); // NodeList chứa cả các khoảng trắng và các <li>
</script>

children – Lấy tất cả các phần tử con (loại bỏ text nodes)

  • children chỉ trả về các phần tử con mà không bao gồm text nodes.

  • Đối tượng trả về là một HTMLCollection (giống mảng nhưng không phải mảng thật sự).

Ví dụ:

<ul id="menu">
    <li>Trang chủ</li>
    <li>Dịch vụ</li>
</ul>
<script>
    let menu = document.getElementById("menu");
    console.log(menu.children); // HTMLCollection chỉ chứa <li>
</script>

firstChild / firstElementChild – Lấy phần tử con đầu tiên

  • firstChild: Trả về node đầu tiên (có thể là text node).

  • firstElementChild: Trả về phần tử con đầu tiên, bỏ qua text nodes.

Ví dụ:

<div id="container">
    <p>Đây là đoạn văn.</p>
</div>
<script>
    let container = document.getElementById("container");
    console.log(container.firstChild); // Có thể là text node nếu có khoảng trắng
    console.log(container.firstElementChild); // Trả về <p>
</script>

lastChild / lastElementChild – Lấy phần tử con cuối cùng

  • lastChild: Trả về node cuối cùng (có thể là text node).

  • lastElementChild: Trả về phần tử con cuối cùng, bỏ qua text nodes.

Ví dụ:

<div id="container">
    <p>Đây là đoạn văn.</p>
</div>
<script>
    let container = document.getElementById("container");
    console.log(container.lastChild); // Có thể là text node nếu có khoảng trắng
    console.log(container.lastElementChild); // Trả về <p>
</script>

Điều hướng theo quan hệ anh chị em (siblings)

Các phần tử trên cùng một cấp (cùng có chung một phần tử cha) được gọi là anh chị em (siblings).

previousSibling / previousElementSibling – Lấy phần tử anh/chị phía trước

  • previousSibling: Lấy node trước phần tử hiện tại (có thể là text node).

  • previousElementSibling: Lấy phần tử trước phần tử hiện tại, bỏ qua text nodes.

Ví dụ:

<ul>
    <li>Trang chủ</li>
    <li id="current">Dịch vụ</li>
    <li>Liên hệ</li>
</ul>
<script>
    let current = document.getElementById("current");
    console.log(current.previousSibling); // Có thể là text node
    console.log(current.previousElementSibling); // Trả về <li>Trang chủ</li>
</script>

nextSibling / nextElementSibling – Lấy phần tử em phía sau

  • nextSibling: Lấy node sau phần tử hiện tại (có thể là text node).

  • nextElementSibling: Lấy phần tử sau phần tử hiện tại, bỏ qua text nodes.

Ví dụ:

<ul>
    <li>Trang chủ</li>
    <li id="current">Dịch vụ</li>
    <li>Liên hệ</li>
</ul>
<script>
    let current = document.getElementById("current");
    console.log(current.nextSibling); // Có thể là text node
    console.log(current.nextElementSibling); // Trả về <li>Liên hệ</li>
</script>

Điều hướng đến phần tử cụ thể

Bên cạnh việc điều hướng theo quan hệ cha - con - anh chị em, ta có thể truy xuất trực tiếp đến phần tử mong muốn bằng các phương thức sau:

getElementById(id) – Lấy phần tử theo ID

  • Tìm kiếm phần tử HTML dựa trên thuộc tính id.

  • Trả về phần tử đầu tiên khớp với id, nếu không tìm thấy sẽ trả về null.

Ví dụ:

<p id="message">Hello World</p>
<script>
    let message = document.getElementById("message");
    console.log(message.textContent); // "Hello World"
</script>

getElementsByClassName(className) – Lấy danh sách phần tử theo class

  • Trả về danh sách tất cả các phần tử có class cụ thể.

  • Kết quả trả về là một HTMLCollection.

Ví dụ:

<p class="text">Đoạn 1</p>
<p class="text">Đoạn 2</p>
<script>
    let texts = document.getElementsByClassName("text");
    console.log(texts[0].textContent); // "Đoạn 1"
</script>

getElementsByTagName(tagName) – Lấy danh sách phần tử theo thẻ

  • Tìm tất cả các phần tử dựa trên tên thẻ (ví dụ: div, p, a).

  • Trả về một HTMLCollection.

Ví dụ:

<p>Đoạn 1</p>
<p>Đoạn 2</p>
<script>
    let paragraphs = document.getElementsByTagName("p");
    console.log(paragraphs.length); // 2
</script>

querySelector(selector) – Lấy phần tử đầu tiên khớp với selector

  • Tìm phần tử theo cú pháp CSS selector (#id, .class, tag).

  • Trả về phần tử đầu tiên tìm thấy.

Ví dụ:

<p class="text">Đoạn 1</p>
<p class="text">Đoạn 2</p>
<script>
    let paragraph = document.querySelector(".text");
    console.log(paragraph.textContent); // "Đoạn 1"
</script>

querySelectorAll(selector) – Lấy danh sách tất cả phần tử khớp với selector

  • Trả về danh sách NodeList chứa tất cả phần tử phù hợp.

Ví dụ:

<p class="text">Đoạn 1</p>
<p class="text">Đoạn 2</p>
<script>
    let paragraphs = document.querySelectorAll(".text");
    console.log(paragraphs.length); // 2
</script>

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

DOM Navigation không chỉ giúp truy xuất và thao tác các phần tử trong trang web mà còn hỗ trợ nhiều ứng dụng quan trọng trong lập trình JavaScript. Dưới đây là các ứng dụng thực tế phổ biến của DOM Navigation:

Thay đổi nội dung động (Dynamic Content)

Một trong những ứng dụng phổ biến nhất của DOM Navigation là thay đổi nội dung trang web mà không cần tải lại trang.

Ví dụ 1: Cập nhật nội dung khi bấm nút

Người dùng nhấn vào nút, nội dung đoạn văn sẽ thay đổi.

<p id="message">Chào bạn!</p>
<button onclick="changeText()">Thay đổi nội dung</button>

<script>
    function changeText() {
        let message = document.getElementById("message");
        message.textContent = "Nội dung đã được cập nhật!";
    }
</script>

Giải thích: Khi bấm nút, JavaScript sẽ tìm đến phần tử có id="message" và thay đổi nội dung của nó.

Ví dụ 2: Hiển thị thời gian thực tế trên trang web

<p id="time">Giờ hiện tại: </p>
<button onclick="showTime()">Xem giờ</button>

<script>
    function showTime() {
        let timeElement = document.getElementById("time");
        let currentTime = new Date().toLocaleTimeString();
        timeElement.textContent = "Giờ hiện tại: " + currentTime;
    }
</script>

Giải thích: Khi bấm nút, JavaScript lấy thời gian hiện tại và cập nhật nội dung của phần tử <p>.

Thay đổi giao diện trang web (CSS & Styles)

DOM Navigation cho phép thay đổi kiểu dáng của phần tử HTML bằng cách chỉnh sửa các thuộc tính CSS.

Ví dụ 1: Thay đổi màu nền của trang

<button onclick="changeBackground()">Đổi màu nền</button>

<script>
    function changeBackground() {
        document.body.style.backgroundColor = "lightblue";
    }
</script>

Giải thích: Khi bấm nút, màu nền của toàn bộ trang web sẽ chuyển thành màu xanh nhạt.

Ví dụ 2: Ẩn và hiện một phần tử

<p id="text">Nội dung có thể ẩn/hiện</p>
<button onclick="toggleVisibility()">Ẩn/Hiện</button>

<script>
    function toggleVisibility() {
        let text = document.getElementById("text");
        if (text.style.display === "none") {
            text.style.display = "block"; // Hiện phần tử
        } else {
            text.style.display = "none"; // Ẩn phần tử
        }
    }
</script>

Giải thích: Khi bấm nút, nội dung sẽ bị ẩn hoặc hiển thị lại.

Thêm/xóa/sửa phần tử trong DOM

DOM Navigation giúp thêm, xóa hoặc chỉnh sửa các phần tử HTML động.

Ví dụ 1: Thêm phần tử vào danh sách

<ul id="list">
    <li>Mục 1</li>
    <li>Mục 2</li>
</ul>
<button onclick="addItem()">Thêm mục</button>

<script>
    function addItem() {
        let list = document.getElementById("list");
        let newItem = document.createElement("li");
        newItem.textContent = "Mục mới";
        list.appendChild(newItem);
    }
</script>

Giải thích: Khi bấm nút, một mục mới sẽ được thêm vào danh sách <ul>.

Ví dụ 2: Xóa một phần tử khỏi danh sách

<ul id="list">
    <li id="item1">Mục 1</li>
    <li>Mục 2</li>
</ul>
<button onclick="removeItem()">Xóa mục đầu</button>

<script>
    function removeItem() {
        let list = document.getElementById("list");
        let firstItem = list.firstElementChild;
        if (firstItem) {
            list.removeChild(firstItem);
        }
    }
</script>

Giải thích: Khi bấm nút, mục đầu tiên trong danh sách sẽ bị xóa.

Ví dụ 3: Chỉnh sửa nội dung phần tử

<p id="paragraph">Đây là đoạn văn.</p>
<button onclick="editText()">Chỉnh sửa</button>

<script>
    function editText() {
        let paragraph = document.getElementById("paragraph");
        paragraph.textContent = "Nội dung đã được chỉnh sửa!";
    }
</script>

Giải thích: Khi bấm nút, đoạn văn sẽ được thay đổi thành nội dung mới.

Xử lý sự kiện (Event Handling) với các phần tử DOM

DOM Navigation giúp truy xuất các phần tử và thêm sự kiện để xử lý các tương tác của người dùng.

Ví dụ 1: Thay đổi màu chữ khi rê chuột vào

<p id="hoverText">Di chuột vào tôi để đổi màu!</p>

<script>
    let text = document.getElementById("hoverText");

    text.addEventListener("mouseover", function () {
        text.style.color = "red";
    });

    text.addEventListener("mouseout", function () {
        text.style.color = "black";
    });
</script>

Giải thích: Khi di chuột vào đoạn văn, chữ sẽ đổi màu đỏ. Khi rời chuột, màu chữ trở lại đen.

Ví dụ 2: Hiển thị thông báo khi nhập vào ô input

<input type="text" id="nameInput" placeholder="Nhập tên của bạn">
<p id="message"></p>

<script>
    let input = document.getElementById("nameInput");
    let message = document.getElementById("message");

    input.addEventListener("input", function () {
        message.textContent = "Bạn đang nhập: " + input.value;
    });
</script>

Giải thích: Khi người dùng nhập vào ô input, nội dung sẽ hiển thị ngay bên dưới.

Ví dụ 3: Kiểm tra độ dài mật khẩu khi nhập

<input type="password" id="password" placeholder="Nhập mật khẩu">
<p id="warning"></p>

<script>
    let password = document.getElementById("password");
    let warning = document.getElementById("warning");

    password.addEventListener("input", function () {
        if (password.value.length < 6) {
            warning.textContent = "Mật khẩu quá ngắn!";
            warning.style.color = "red";
        } else {
            warning.textContent = "Mật khẩu hợp lệ.";
            warning.style.color = "green";
        }
    });
</script>

Giải thích: Khi nhập mật khẩu, nếu độ dài nhỏ hơn 6 ký tự, thông báo cảnh báo sẽ hiển thị màu đỏ.

Lưu ý khi sử dụng DOM Navigation trong JavaScript

Khi thao tác với DOM trong JavaScript, có một số điểm quan trọng cần lưu ý để đảm bảo hiệu suất, bảo mật và khả năng mở rộng của ứng dụng. Dưới đây là ba lưu ý quan trọng:

Hiệu suất khi truy cập DOM

Giảm số lần truy cập DOM

Mỗi lần truy cập vào DOM (ví dụ: document.getElementById() hoặc element.childNodes[]), trình duyệt phải thực hiện tìm kiếm và xử lý dữ liệu, điều này có thể làm giảm hiệu suất nếu truy cập quá thường xuyên.

Không tối ưu:

for (let i = 0; i < 1000; i++) {
    document.getElementById("counter").textContent = i;
}

Lỗi: Mỗi vòng lặp sẽ tìm kiếm phần tử counter trong DOM, gây tốn tài nguyên.

Cách tối ưu:

let counterElement = document.getElementById("counter");
for (let i = 0; i < 1000; i++) {
    counterElement.textContent = i;
}

Giải pháp: Lưu trữ phần tử DOM vào một biến để sử dụng lại thay vì truy cập trực tiếp nhiều lần.

Sử dụng Document Fragment để giảm reflow & repaint

Reflow xảy ra khi trình duyệt phải tính toán lại bố cục trang khi có thay đổi trong DOM. Nếu thêm nhiều phần tử vào DOM một cách trực tiếp, trình duyệt sẽ thực hiện nhiều lần reflow.

Không tối ưu:

let list = document.getElementById("list");
for (let i = 0; i < 1000; i++) {
    let newItem = document.createElement("li");
    newItem.textContent = "Mục " + i;
    list.appendChild(newItem);
}

Lỗi: Mỗi lần thêm một <li>, trình duyệt phải cập nhật lại giao diện, làm giảm hiệu suất.

Cách tối ưu:

let list = document.getElementById("list");
let fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
    let newItem = document.createElement("li");
    newItem.textContent = "Mục " + i;
    fragment.appendChild(newItem);
}

list.appendChild(fragment);

Giải pháp: Sử dụng DocumentFragment để tạo tất cả các phần tử trước, sau đó chèn vào DOM một lần duy nhất, giúp tối ưu hiệu suất.

Sử dụng Delegation Event để tối ưu xử lý sự kiện

Nếu có nhiều phần tử cùng loại cần lắng nghe sự kiện (ví dụ: danh sách sản phẩm, danh mục), thay vì gán sự kiện riêng lẻ cho từng phần tử, hãy gán sự kiện cho phần tử cha và sử dụng event.target để xác định phần tử con nào được click.

Không tối ưu:

let items = document.querySelectorAll(".item");
items.forEach(item => {
    item.addEventListener("click", function () {
        alert("Bạn đã chọn " + this.textContent);
    });
});

Lỗi: Nếu có 1000 phần tử, trình duyệt sẽ tạo 1000 sự kiện, gây tốn bộ nhớ.

Cách tối ưu:

document.getElementById("list").addEventListener("click", function (event) {
    if (event.target.classList.contains("item")) {
        alert("Bạn đã chọn " + event.target.textContent);
    }
});

Giải pháp: Gán sự kiện cho phần tử cha (#list), sau đó kiểm tra event.target để xác định phần tử con nào được click.

Phân biệt giữa Node và Element trong DOM

Khi điều hướng DOM, bạn có thể gặp các thuật ngữ NodeElement. Hiểu rõ sự khác biệt giúp bạn chọn phương pháp truy cập chính xác.

Thuộc tính childNodes children
Trả về Tất cả các node con (bao gồm text, comment, element) Chỉ trả về các phần tử con (element)
Loại dữ liệu NodeList HTMLCollection
Ví dụ element.childNodes element.children

Ví dụ sai: Sử dụng childNodes gây lỗi khi duyệt qua phần tử con

let parent = document.getElementById("parent");
let nodes = parent.childNodes; 

nodes.forEach(node => { 
    console.log(node.textContent);  // Có thể gặp lỗi nếu node là text hoặc comment
});

Lỗi: childNodes bao gồm cả text node (dấu xuống dòng, khoảng trắng), gây lỗi nếu xử lý như element.

Ví dụ đúng: Sử dụng children để lấy danh sách các phần tử con hợp lệ

let parent = document.getElementById("parent");
let elements = parent.children;  

for (let element of elements) {
    console.log(element.textContent); // Chỉ in nội dung của các phần tử con
}

Giải pháp: Sử dụng children thay vì childNodes để tránh lấy các text node không cần thiết.

Hạn chế sử dụng innerHTML để tránh rủi ro bảo mật

Nguy cơ XSS (Cross-Site Scripting) khi sử dụng innerHTML

Nếu bạn sử dụng innerHTML để chèn nội dung mà không kiểm tra đầu vào, hacker có thể chèn mã JavaScript độc hại vào trang web.

Ví dụ nguy hiểm:

document.getElementById("output").innerHTML = userInput;

Lỗi: Nếu userInput<script>alert('Bị tấn công!');</script>, trình duyệt sẽ thực thi mã JavaScript này.

Cách an toàn:
Thay vì dùng innerHTML, hãy sử dụng textContent hoặc createElement để ngăn chặn mã độc.

let output = document.getElementById("output");
let userText = document.createTextNode(userInput);
output.appendChild(userText);

Giải pháp: Sử dụng createTextNode() giúp bảo vệ trang web khỏi XSS.

innerHTML gây ảnh hưởng đến hiệu suất

Mỗi khi bạn đặt innerHTML, trình duyệt phải phân tích lại toàn bộ nội dung HTML, điều này có thể làm giảm hiệu suất.

Ví dụ gây giảm hiệu suất:

document.getElementById("list").innerHTML += "<li>Mục mới</li>";

Lỗi: Mỗi lần thêm một mục mới, trình duyệt sẽ phân tích lại toàn bộ danh sách.

Cách tối ưu:

let newItem = document.createElement("li");
newItem.textContent = "Mục mới";
document.getElementById("list").appendChild(newItem);

Giải pháp: Sử dụng createElement thay vì innerHTML để cải thiện hiệu suất.

Kết bài

DOM Navigation là một kỹ thuật quan trọng trong JavaScript, giúp lập trình viên dễ dàng truy cập, thao tác và thay đổi nội dung của trang web một cách linh hoạt. Bằng cách sử dụng các phương pháp điều hướng phù hợp, bạn có thể tối ưu hóa hiệu suất, đảm bảo trải nghiệm người dùng mượt mà và tránh các rủi ro bảo mật không đáng có.

Tuy nhiên, khi làm việc với DOM, cần lưu ý hạn chế số lần truy cập DOM để tránh ảnh hưởng đến hiệu suất, phân biệt rõ giữa NodeElement, đồng thời tránh lạm dụng innerHTML để giảm thiểu nguy cơ tấn công XSS.

Việc hiểu và áp dụng đúng các nguyên tắc của DOM Navigation sẽ giúp bạn xây dựng các ứng dụng web hiệu quả, tối ưu và bảo mật hơn. Hãy thực hành thường xuyên để làm chủ kỹ thuật này và nâng cao kỹ năng lập trình JavaScript của bạn!

Bài viết liên quan