Hãy xem xét kỹ hơn về các nút DOM.
Trong chương này, chúng ta sẽ tìm hiểu sâu hơn về chúng và học các thuộc tính được sử dụng nhiều nhất.

1. Các lớp nút DOM

Các nút DOM khác nhau có thể có các thuộc tính khác nhau. Ví dụ, một nút phần tử tương ứng với thẻ có các thuộc tính liên quan đến liên kết, và một nút tương ứng với <input> có các thuộc tính liên quan đến đầu vào, v.v. Các nút văn bản không giống như các nút phần tử. Nhưng cũng có các thuộc tính và phương thức chung giữa tất cả chúng, vì tất cả các lớp nút DOM tạo thành một hệ thống phân cấp duy nhất.
Mỗi nút DOM thuộc về lớp tích hợp sẵn tương ứng.
Gốc của hệ thống phân cấp là EventTarget, được kế thừa bởi Node, và các nút DOM khác kế thừa từ đó.
Đây là hình ảnh, giải thích sẽ theo sau:



Các lớp bao gồm:
EventTarget — là lớp “trừu tượng” gốc cho mọi thứ.
Các đối tượng của lớp này không bao giờ được tạo ra. Nó đóng vai trò là nền tảng, để tất cả các nút DOM hỗ trợ cái gọi là “sự kiện”, chúng ta sẽ tìm hiểu sau.
Node — cũng là một lớp “trừu tượng”, đóng vai trò là nền tảng cho các nút DOM.

Nó cung cấp các chức năng cây cốt lõi: parentNode, nextSibling, childNodes và các phương thức tương tự (chúng là các getter). Các đối tượng của lớp Node không bao giờ được tạo ra. Nhưng có các lớp khác kế thừa từ nó (và do đó kế thừa chức năng của Node).
Document, vì lý do lịch sử thường được kế thừa bởi HTMLDocument (dù bản đặc tả mới nhất không yêu cầu điều này) — là một tài liệu tổng thể.

Đối tượng toàn cục document thuộc chính lớp này. Nó đóng vai trò là điểm truy cập vào DOM.
CharacterData — một lớp “trừu tượng”, được kế thừa bởi:

Text — lớp tương ứng với văn bản bên trong các phần tử, ví dụ như Hello trong <p>Hello</p>

. – Comment — lớp dành cho các nhận xét. Chúng không được hiển thị, nhưng mỗi nhận xét trở thành một phần tử của DOM.

  • Element — là lớp cơ sở cho các phần tử DOM.

    Nó cung cấp các phương thức điều hướng cấp phần tử như nextElementSibling, children và các phương thức tìm kiếm như getElementsByTagName, querySelector.
    Một trình duyệt hỗ trợ không chỉ HTML, mà còn XML và SVG. Vì vậy, lớp Element đóng vai trò là nền tảng cho các lớp cụ thể hơn: SVGElement, XMLElement (chúng ta không cần đến chúng ở đây) và HTMLElement.

  • Cuối cùng, HTMLElement là lớp cơ bản cho tất cả các phần tử HTML. Chúng ta sẽ làm việc với nó phần lớn thời gian.

Có nhiều thẻ khác với các lớp riêng của chúng có thể có các thuộc tính và phương thức cụ thể, trong khi một số phần tử, chẳng hạn như ,

Có nhiều thẻ khác với các lớp riêng của chúng có thể có các thuộc tính và phương thức cụ thể, trong khi một số phần tử như <span>, <section>, <article> không có thuộc tính cụ thể nào, vì vậy chúng là các thể hiện của lớp HTMLElement.

Ví dụ, hãy xem xét đối tượng DOM cho một phần tử <input>. Nó thuộc lớp HTMLInputElement.

Nó nhận các thuộc tính và phương thức như một sự chồng chéo của (liệt kê theo thứ tự kế thừa):

  • HTMLInputElement — lớp này cung cấp các thuộc tính cụ thể cho đầu vào,
  • HTMLElement — nó cung cấp các phương thức của phần tử HTML chung (và các getter/setter),
  • Element — cung cấp các phương thức của phần tử chung,
  • Node — cung cấp các thuộc tính của nút DOM chung,
  • EventTarget — cung cấp hỗ trợ cho các sự kiện (sẽ được đề cập sau),
    Để xem tên lớp của nút DOM, chúng ta có thể nhớ rằng một đối tượng thường có thuộc tính constructor. Nó tham chiếu đến hàm tạo lớp, và constructor.name là tên của nó:

alert( document.body.constructor.name ); // HTMLBodyElement

…Hoặc chúng ta có thể chỉ cần toString nó:


alert( document.body ); // [object HTMLBodyElement]

Chúng ta cũng có thể sử dụng instanceof để kiểm tra kế thừa:


alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true

Như chúng ta thấy, các nút DOM là các đối tượng JavaScript thông thường. Chúng sử dụng các lớp dựa trên prototype để kế thừa.
Điều đó cũng dễ thấy bằng cách xuất một phần tử với console.dir(elem) trong trình duyệt. Tại đó trong bảng điều khiển, bạn có thể thấy HTMLElement.prototype, Element.prototype, và v.v.

Hầu hết các trình duyệt hỗ trợ hai lệnh trong công cụ phát triển của chúng: console.logconsole.dir. Chúng xuất các đối số của chúng ra bảng điều khiển. Đối với các đối tượng JavaScript, các lệnh này thường làm điều giống nhau.

Nhưng đối với các phần tử DOM thì chúng khác nhau:

  • console.log(elem) hiển thị cây DOM của phần tử.
  • console.dir(elem) hiển thị phần tử dưới dạng một đối tượng DOM, tốt để khám phá các thuộc tính của nó.
    Hãy thử trên document.body.
  • Trong tài liệu kỹ thuật, các lớp DOM không được mô tả bằng JavaScript, mà bằng một ngôn ngữ mô tả đặc biệt Interface description language (IDL), thường dễ hiểu.

Trong IDL, tất cả các thuộc tính đều được thêm tiền tố với loại của chúng. Ví dụ, DOMString, boolean và v.v.

Đây là một đoạn trích từ nó, với các chú thích:

// Define HTMLInputElement
*!*
// The colon ":" means that HTMLInputElement inherits from HTMLElement
*/!*
interface HTMLInputElement: HTMLElement {
  // here go properties and methods of <input> elements

*!*
  // "DOMString" means that the value of a property is a string
*/!*
  attribute DOMString accept;
  attribute DOMString alt;
  attribute DOMString autocomplete;
  attribute DOMString value;

*!*
  // boolean value property (true/false)
  attribute boolean autofocus;
*/!*
  ...
*!*
  // now the method: "void" means that the method returns no value
*/!*
  void select();
  ...
}

2. Thuộc tính “nodeType”

Thuộc tính nodeType cung cấp một cách “cổ điển” hơn để xác định “loại” của một nút DOM.
Nó có giá trị số:

  • elem.nodeType == 1 cho các nút phần tử,
  • elem.nodeType == 3 cho các nút văn bản,
  • elem.nodeType == 9 cho đối tượng tài liệu,
  • có một số giá trị khác trong tài liệu kỹ thuật.
    Ví dụ:

<body>
  <script>
  let elem = document.body;

  // let's examine: what type of node is in elem?
  alert(elem.nodeType); // 1 => element

  // and its first child is...
  alert(elem.firstChild.nodeType); // 3 => text

  // for the document object, the type is 9
  alert( document.nodeType ); // 9
  </script>
</body>

Trong các script hiện đại, chúng ta có thể sử dụng instanceof và các kiểm tra dựa trên lớp khác để xem loại nút, nhưng đôi khi nodeType có thể đơn giản hơn. Chúng ta chỉ có thể đọc nodeType, không thể thay đổi nó.

3. Thẻ: nodeName và tagName

Đưa ra một nút DOM, chúng ta có thể đọc tên thẻ của nó từ các thuộc tính nodeName hoặc tagName:
Ví dụ:

run
alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY

Có sự khác biệt nào giữa tagNamenodeName không?
Chắc chắn, sự khác biệt được phản ánh trong tên của chúng, nhưng thực sự có phần tinh tế.
– Thuộc tính tagName chỉ tồn tại cho các nút Element.

  • nodeName được định nghĩa cho bất kỳ nút nào (Node):
    • – đối với các phần tử, nó có nghĩa là giống như tagName.
  • – đối với các loại nút khác (văn bản, chú thích, v.v.), nó có một chuỗi với loại nút.
    Nói cách khác, tagName chỉ được hỗ trợ bởi các nút phần tử (vì nó xuất phát từ lớp Element), trong khi nodeName có thể cung cấp thông tin về các loại nút khác.
    Ví dụ, hãy so sánh tagNamenodeName cho đối tượng document và một nút chú thích:

<body>

  <script>
    // for comment
    alert( document.body.firstChild.tagName ); // undefined (not an element)
    alert( document.body.firstChild.nodeName ); // #comment

    // for document
    alert( document.tagName ); // undefined (not an element)
    alert( document.nodeName ); // #document
  </script>
</body>

Nếu chúng ta chỉ làm việc với các phần tử, thì chúng ta có thể sử dụng cả tagNamenodeName – không có sự khác biệt.
Trình duyệt có hai chế độ xử lý tài liệu: HTML và XML. Thông thường, chế độ HTML được sử dụng cho các trang web. Chế độ XML được kích hoạt khi trình duyệt nhận được tài liệu XML với tiêu đề: Content-Type: application/xml+xhtml.

Trong chế độ HTML, tagName/nodeName luôn được viết hoa: đó là BODY cho cả <body> hoặc <BoDy>.

Trong chế độ XML, chữ hoa chữ thường được giữ nguyên. Ngày nay, chế độ XML hiếm khi được sử dụng.

4. innerHTML: nội dung

Thuộc tính innerHTML cho phép lấy HTML bên trong phần tử dưới dạng chuỗi.
Chúng ta cũng có thể sửa đổi nó. Vì vậy, đây là một trong những cách mạnh mẽ nhất để thay đổi trang.

Ví dụ cho thấy nội dung của document.body và sau đó thay thế hoàn toàn nó:


<body>
  <p>A paragraph</p>
  <div>A div</div>

  <script>
    alert( document.body.innerHTML ); // read the current contents
    document.body.innerHTML = 'The new BODY!'; // replace it
  </script>

</body>

Chúng ta có thể thử chèn HTML không hợp lệ, trình duyệt sẽ sửa lỗi của chúng ta:


<body>

  <script>
    document.body.innerHTML = '<b>test'; // forgot to close the tag
    alert( document.body.innerHTML ); // <b>test</b> (fixed)
  </script>

</body>

Nếu innerHTML chèn một thẻ <script>

5.Cẩn thận: Sử dụng innerHTML += thực hiện việc ghi đè toàn bộ nội dung.

Chúng ta có thể thêm HTML vào một phần tử bằng cách sử dụng elem.innerHTML += "thêm html".

Ví dụ như sau:

chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "How goes?";

Tuy nhiên, chúng ta cần hết sức cẩn thận khi làm điều này, vì thực tế nó không phải là thêm mà là ghi đè toàn bộ.

Về mặt kỹ thuật, hai dòng này thực hiện cùng một việc:

elem.innerHTML += "...";
// là cách viết ngắn hơn của:
elem.innerHTML = elem.innerHTML + "..."

Nói cách khác, innerHTML += thực hiện như sau:

  • Nội dung cũ bị xóa.
  • Nội dung mới của innerHTML được viết lại (là sự kết hợp giữa nội dung cũ và mới).

Vì nội dung bị “xóa trắng” và viết lại từ đầu, nên tất cả hình ảnh và các tài nguyên khác sẽ được tải lại.

Trong ví dụ chatDiv ở trên, dòng chatDiv.innerHTML += "How goes?" sẽ tái tạo lại nội dung HTML và tải lại smile.gif (hy vọng là nó đã được lưu vào bộ nhớ cache). Nếu chatDiv có nhiều văn bản và hình ảnh khác, thì việc tải lại sẽ trở nên rõ ràng hơn.

Cũng có những tác dụng phụ khác. Ví dụ, nếu văn bản hiện tại đã được chọn bằng chuột, thì hầu hết các trình duyệt sẽ bỏ chọn văn bản khi innerHTML được ghi đè lại. Và nếu có một thẻ <input> với văn bản đã được người dùng nhập vào, thì văn bản đó sẽ bị xóa. Và còn nhiều điều khác nữa.

May mắn thay, có những cách khác để thêm HTML ngoài innerHTML, và chúng ta sẽ nghiên cứu chúng sớm.

5.outerHTML: toàn bộ HTML của phần tử.

Thuộc tính outerHTML chứa toàn bộ HTML của phần tử. Nó giống như innerHTML cộng với chính phần tử đó.

Dưới đây là một ví dụ:

<div id="elem">Hello <b>World</b></div>

<script>
  alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>

Lưu ý: khác với innerHTML, việc ghi vào outerHTML không thay đổi phần tử mà thay vào đó thay thế nó trong DOM.

Có vẻ hơi lạ, và đúng là như vậy, đó là lý do chúng tôi ghi chú riêng về điều này ở đây. Hãy xem xét ví dụ sau:

<div>Hello, world!</div>

<script>
  let div = document.querySelector('div');

  // Thay thế div.outerHTML bằng <p>...</p>
  div.outerHTML = '<p>A new element</p>'; // (*)

  // Wow! 'div' vẫn như cũ!
  alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>

Có vẻ thực sự kỳ lạ, đúng không?

Trong dòng () chúng ta đã thay thế div bằng <p>A new element</p>. Trong tài liệu ngoài (DOM), chúng ta thấy nội dung mới thay vì <div>. Nhưng, như chúng ta thấy ở dòng (*), giá trị của biến div cũ không thay đổi!

Gán outerHTML không sửa đổi phần tử DOM (đối tượng được tham chiếu bởi, trong trường hợp này, biến div), mà loại bỏ nó khỏi DOM và chèn HTML mới vào chỗ của nó.

Vậy điều gì đã xảy ra khi gán div.outerHTML = ...:

  • div đã bị loại bỏ khỏi tài liệu.
  • Một đoạn HTML khác <p>A new element</p> đã được chèn vào chỗ của nó.
  • div vẫn giữ giá trị cũ của nó. HTML mới không được lưu vào bất kỳ biến nào.

Rất dễ mắc lỗi ở đây: thay đổi div.outerHTML rồi tiếp tục làm việc với div như thể nó đã có nội dung mới. Nhưng nó không có. Điều này đúng với innerHTML, nhưng không đúng với outerHTML.

Chúng ta có thể ghi vào elem.outerHTML, nhưng cần lưu ý rằng điều này không thay đổi phần tử mà chúng ta đang ghi vào (elem). Nó chỉ thay thế HTML mới vào chỗ của nó. Chúng ta có thể lấy các tham chiếu đến các phần tử mới bằng cách truy vấn DOM.

6. nodeValue/data: Nội dung của nút văn bản

Thuộc tính innerHTML chỉ hợp lệ cho các nút phần tử.

Các loại nút khác, chẳng hạn như nút văn bản, có các thuộc tính tương ứng: nodeValuedata. Hai thuộc tính này gần như giống nhau về mặt thực tế, chỉ có sự khác biệt nhỏ trong đặc tả. Vì vậy, chúng ta sẽ sử dụng data, vì nó ngắn gọn hơn.

Ví dụ về việc đọc nội dung của một nút văn bản và một chú thích:

<body>
  Hello
  <!-- Comment -->
  <script>
    let text = document.body.firstChild;
    alert(text.data); // Hello

    let comment = text.nextSibling;
    alert(comment.data); // Comment
  </script>
</body>

Đối với các nút văn bản, chúng ta có thể tưởng tượng lý do để đọc hoặc sửa đổi chúng, nhưng tại sao lại là các chú thích?

Đôi khi, các nhà phát triển chèn thông tin hoặc hướng dẫn mẫu vào HTML trong các chú thích, như sau:

<!-- if isAdmin -->
  <div>Welcome, Admin!</div>
<!-- /if -->

… Sau đó JavaScript có thể đọc nó từ thuộc tính data và xử lý các hướng dẫn nhúng.

7. textContent: Văn bản thuần túy

textContent cung cấp quyền truy cập vào văn bản bên trong phần tử: chỉ văn bản, không bao gồm các thẻ <tags>.

Ví dụ:

<div id="news">
  <h1>Headline!</h1>
  <p>Martians attack people!</p>
</div>

<script>
  // Headline! Martians attack people!
  alert(news.textContent);
</script>

Như chúng ta thấy, chỉ văn bản được trả về, giống như tất cả các thẻ <tags> đã bị cắt bỏ, nhưng văn bản trong chúng vẫn được giữ lại.

Trong thực tế, việc đọc văn bản như vậy là hiếm khi cần thiết.

Việc ghi vào textContent hữu ích hơn nhiều, vì nó cho phép viết văn bản theo cách “an toàn”.

Giả sử chúng ta có một chuỗi tùy ý, chẳng hạn như do người dùng nhập vào, và muốn hiển thị nó.

  • Với innerHTML, chúng ta sẽ chèn nó “như HTML”, với tất cả các thẻ HTML.
  • Với textContent, chúng ta sẽ chèn nó “như văn bản”, tất cả các ký hiệu được coi là văn bản thuần túy.

So sánh hai trường hợp:

<div id="elem1"></div>
<div id="elem2"></div>

<script>
  let name = prompt("What's your name?", "<b>Winnie-the-Pooh!</b>");

  elem1.innerHTML = name;
  elem2.textContent = name;
</script>
  • <div> đầu tiên nhận tên “như HTML”: tất cả các thẻ trở thành thẻ, vì vậy chúng ta thấy tên in đậm.
  • <div> thứ hai nhận tên “như văn bản”, vì vậy chúng ta thấy <b>Winnie-the-Pooh!</b> theo đúng nghĩa đen.

Trong hầu hết các trường hợp, chúng ta mong đợi văn bản từ người dùng và muốn coi nó là văn bản. Chúng ta không muốn HTML không mong muốn xuất hiện trên trang web của mình. Gán giá trị cho textContent thực hiện chính xác điều đó.

9. Thuộc tính “ẩn”

Thuộc tính và thuộc tính DOM “hidden” xác định liệu phần tử có hiển thị hay không.

Chúng ta có thể sử dụng nó trong HTML hoặc gán nó bằng JavaScript, như sau:

<div>Both divs below are hidden</div>

<div hidden>With the attribute "hidden"</div>

<div id="elem">JavaScript assigned the property "hidden"</div>

<script>
  elem.hidden = true;
</script>

Về mặt kỹ thuật, hidden hoạt động giống như style="display:none". Nhưng nó ngắn gọn hơn khi viết.

Dưới đây là một phần tử nhấp nháy:

<div id="elem">A blinking element</div>

<script>
  setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>

10. Các thuộc tính khác

Các phần tử DOM cũng có các thuộc tính bổ sung, đặc biệt là những thuộc tính phụ thuộc vào lớp:

  • value – giá trị cho <input>, <select><textarea> (HTMLInputElement, HTMLSelectElement…).
  • href – “href” cho <a href="..."> (HTMLAnchorElement).
  • id – giá trị của thuộc tính “id”, cho tất cả các phần tử (HTMLElement).
  • …và nhiều hơn nữa…

Ví dụ:

<input type="text" id="elem" value="value">

<script>
  alert(elem.type); // "text"
  alert(elem.id); // "elem"
  alert(elem.value); // value
</script>

Hầu hết các thuộc tính HTML chuẩn đều có thuộc tính DOM tương ứng, và chúng ta có thể truy cập chúng như vậy.

Nếu chúng ta muốn biết danh sách đầy đủ các thuộc tính hỗ trợ cho một lớp cụ thể, chúng ta có thể tìm thấy chúng trong đặc tả. Ví dụ, HTMLInputElement được tài liệu tại https://html.spec.whatwg.org/#htmlinputelement.

Hoặc nếu chúng ta muốn có chúng nhanh chóng hoặc quan tâm đến đặc tả của trình duyệt cụ thể – chúng ta có thể luôn xuất phần tử bằng cách sử dụng console.dir(elem) và đọc các thuộc tính. Hoặc khám phá “DOM properties” trong tab Elements của công cụ phát triển trình duyệt.

11. Tóm tắt

Mỗi nút DOM thuộc về một lớp nhất định. Các lớp này tạo thành một hệ thống phân cấp. Tập hợp đầy đủ các thuộc tính và phương thức là kết quả của kế thừa.

Các thuộc tính chính của nút DOM bao gồm:

  • nodeType:
  • Cho phép xác định xem một nút là nút văn bản hay nút phần tử. Nó có giá trị số: 1 cho phần tử, 3 cho nút văn bản, và một số khác cho các loại nút khác. Chỉ đọc.
  • nodeName/tagName:
  • Đối với các phần tử, tên thẻ (chữ hoa trừ khi ở chế độ XML). Đối với các nút không phải phần tử, nodeName mô tả nó là gì. Chỉ đọc.
  • innerHTML:
  • Nội dung HTML của phần tử. Có thể được sửa đổi.
  • outerHTML:
  • HTML đầy đủ của phần tử. Việc ghi vào elem.outerHTML không ảnh hưởng trực tiếp đến elem mà thay vào đó, nó sẽ bị thay thế bằng HTML mới trong ngữ cảnh bên ngoài.
  • nodeValue/data:
  • Nội dung của một nút không phải phần tử (văn bản, chú thích). Hai thuộc tính này gần như giống nhau, thường dùng data. Có thể được sửa đổi.
  • textContent:
  • Văn bản bên trong phần tử: chỉ văn bản, không bao gồm các thẻ <tags>. Việc ghi vào thuộc tính này sẽ đưa văn bản vào phần tử, với tất cả các ký tự đặc biệt và thẻ được xử lý chính xác như văn bản. Có thể chèn văn bản do người dùng tạo và bảo vệ khỏi việc chèn HTML không mong muốn.
  • hidden:
  • Khi được đặt thành true, thực hiện giống như CSS display:none.

Các nút DOM cũng có các thuộc tính khác tùy thuộc vào lớp của chúng. Ví dụ, các phần tử <input> (HTMLInputElement) hỗ trợ value, type, trong khi các phần tử <a> (HTMLAnchorElement) hỗ trợ href, v.v. Hầu hết các thuộc tính HTML chuẩn có thuộc tính DOM tương ứng.

Tuy nhiên, các thuộc tính HTML và thuộc tính DOM không phải lúc nào cũng giống nhau, như chúng ta sẽ thấy trong chương tiếp theo.

Kết thúc hành trình khám phá các thuộc tính của node trong JavaScript, từ `type`, `tag`, đến `contents`, bạn đã nắm bắt được những công cụ mạnh mẽ để làm việc với DOM. Với những hiểu biết này, bạn có thể kiểm soát và thao tác các phần tử HTML hiệu quả hơn. Cảm ơn bạn đã theo dõi bài viết từ Cafedev. Hãy tiếp tục khám phá và áp dụng kiến thức này để nâng cao kỹ năng lập trình JavaScript của bạn!

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Javascrypt chi tiết, dễ hiểu từ cơ bản tới nâng cao + Full Bài Tập thực hành nâng cao trình dev

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!