Hãy cùng tìm hiểu sâu hơn về các nút DOM.

Trong chương này, cafedev sẽ xem thêm về chúng là gì và tìm hiểu các thuộc tính được sử dụng nhiều nhất của chúng.

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ẻ <a> có các thuộc tính liên quan đến liên kết và 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ó những thuộc tính và phương thức chung giữa tất cả chúng, bởi vì tất cả các lớp của các 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 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ừ nó.

Đây là hình ảnh, giải thích để làm theo:

Các lớp là:

  • EventTarget – là lớp “trừu tượng” gốc. Các đối tượng của lớp đó không bao giờ được tạo. Nó đóng vai trò như một cơ sở để tất cả các nút DOM hỗ trợ cái gọi là “sự kiện”, chúng ta sẽ nghiên cứu chúng sau.
  • Node – cũng là một lớp “trừu tượng”, đóng vai trò là cơ sở cho các nút DOM. Nó cung cấp các chức năng cây cốt lõi: parentNode, nextSibling, childNodesvà vân vân (họ là thu khí). Các đối tượng của Nodelớp không bao giờ được tạo. Nhưng có những lớp nút cụ thể kế thừa từ nó, đó là: Textcho các nút văn bản, Elementcho các nút phần tử và những lớp kỳ lạ hơn như Commentcho các nút chú thích.
  • Phần tử(Element) – là một lớp cơ sở cho các phần tử DOM. Nó cung cấp hướng yếu tố cấp như nextElementSibling, childrenvà phương pháp tìm kiếm thích getElementsByTagName, querySelector. Một trình duyệt không chỉ hỗ trợ HTML mà còn hỗ trợ cả XML và SVG. Các Elementlớp học phục vụ như một cơ sở cho các lớp cụ thể hơn: SVGElement, XMLElementvà HTMLElement.
  • HTMLElement – cuối cùng là lớp cơ bản cho tất cả các phần tử HTML. Nó được kế thừa bởi các phần tử HTML cụ thể:
    • HTMLInputElement – lớp cho các phần tử <input>,
    • HTMLBodyElement – lớp cho  các phần tử <body>,
    • HTMLAnchorElement – lớp cho các phần tử <a>,
    • … Và v.v., mỗi thẻ có lớp riêng có thể cung cấp các thuộc tính và phương thức cụ thể.

Vì vậy, tập hợp đầy đủ các thuộc tính và phương thức của một nút nhất định là kết quả của sự kế thừa.

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

Nó nhận các thuộc tính và phương thức dưới dạng chồng chất  (được liệt kê theo thứ tự kế thừa):

  • HTMLInputElement – lớp này cung cấp các thuộc tính đầu vào cụ thể,
  • HTMLElement – nó cung cấp các phương thức phần tử HTML phổ biến (và getters / setters),
  • Element – cung cấp các phương pháp phần tử chung,
  • Node – cung cấp các thuộc tính nút DOM chung,
  • EventTarget – hỗ trợ cho các sự kiện (sẽ được đề cập sau),
  • … Và cuối cùng nó kế thừa từ Object, vì vậy các phương thức “đối tượng thuần túy” như hasOwnProperty cũng có sẵn.

Để 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 toString nó:

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

Chúng ta cũng có thể sử dụng instanceof để kiểm tra tính 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 có thể thấy, các nút DOM là các đối tượng JavaScript thông thường. Họ sử dụng các lớp dựa trên nguyên mẫu để kế thừa.

Điều đó cũng dễ dàng nhận thấy bằng cách xuất một phần tử bằng console.dir(elem) của trình duyệt. Ở đó trong console, bạn có thể thấy HTMLElement.prototype, Element.prototypev.v.

console.dir(elem) đấu với console.log(elem)

Hầu hết các trình duyệt hỗ trợ hai lệnh trong các công cụ dành cho developer của họ: console.log và console.dir. Họ xuất các đối số của họ ra console. Đối với các đối tượng JavaScript, các lệnh này thường làm như vậy.

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

  • console.log(elem) hiển thị cây DOM phần tử.
  • console.dir(elem) hiển thị phần tử dưới dạng đối tượng DOM, rất tốt để khám phá các thuộc tính của nó.

Hãy thử nó trên document.body.

IDL trong thông số kỹ thuật

Trong đặc tả, các lớp DOM không được mô tả bằng JavaScript, mà là một ngôn ngữ mô tả Giao diện đặc biệt (IDL), thường dễ hiểu.

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

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

// Define HTMLInputElement

// 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”

Các nodeType  cung cấp thêm một “old-fashioned” cách để có được những “loại” của một nút DOM.

Nó có một 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 đặc điểm kỹ thuật .

Ví dụ:

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

  // let's examine what it is?
  alert(elem.nodeType); // 1 => element

  // and the 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 tập lệnh hiện đại, chúng ta có thể sử dụng instanceof và các bài 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. Tag: nodeName và tagName

Với một nút DOM, chúng ta có thể đọc tên thẻ của nó từ nodeName hoặc tagName với các thuộc tính:

Ví dụ:

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

Có sự khác biệt nào giữa tagName và nodeName?

Chắc chắn, sự khác biệt được phản ánh trong tên của họ, nhưng thực sự là một chút tinh tế.

  • Các tagName chỉ tồn tại cho các nút Element.
  • Các nodeName được định nghĩa cho bất kỳ Node:
    • đối với các phần tử nó có nghĩa giống như tagName.
    • đối với các loại nút khác (văn bản, nhận xét, 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ó bắt nguồn từ lớp Element), trong khi nodeName có thể nói điều gì đó về các loại nút khác.

Ví dụ: hãy so sánh tagName và nodeName cho document và một nút nhận xét(comment):

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

<body><!-- comment -->

  <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ỉ xử lý các phần tử, thì chúng ta có thể sử dụng cả hai tagName và nodeName- không có sự khác biệt.

Tên thẻ luôn là chữ hoa ngoại trừ trong chế độ XML

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. XML-mode được kích hoạt khi trình duyệt nhận được một XML tài liệu với tiêu đề: Content-Type: application/xml+xhtml.

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

Trong chế độ XML, trường hợp được giữ nguyên “nguyên trạng”. Ngày nay chế độ XML hiếm khi được sử dụng.

4. innerHTML: nội dung

Các innerHTML sở hữu cho phép để có được code HTML bên trong phần tử như là một chuỗi.

Chúng ta cũng có thể sửa đổi nó. Vì vậy, đó là một trong những cách mạnh mẽ nhất để thay đổi trang.

Ví dụ hiển thị nội dung của document.body và sau đó thay thế hoàn toà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ể cố gắng 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>

Tập lệnh không thực thi

Nếu innerHTML chèn một thẻ <script> vào tài liệu – nó sẽ trở thành một phần của HTML, nhưng không thực thi.

5. Lưu ý: “innerHTML + =” ghi đè hoàn toàn

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

Như thế này:

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

Nhưng chúng ta nên hết sức cẩn thận khi làm điều đó, bởi vì những gì đang diễn ra không phải là một sự bổ sung, mà là một sự ghi đè hoàn toàn.

Về mặt kỹ thuật, hai dòng này làm như nhau:

elem.innerHTML += "...";
// is a shorter way to write:
elem.innerHTML = elem.innerHTML + "..."

Nói cách khác, innerHTML+=thực hiện điều này:

  1. Nội dung cũ bị xóa.
  2. Cái mới innerHTML được viết để thay thế (ghép giữa cái cũ và cái mới).

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

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

Cũng có những tác dụng phụ khác. Ví dụ: nếu văn bản hiện có được chọn bằng chuột, thì hầu hết các trình duyệt sẽ xóa lựa chọn khi viết lại innerHTML. Và nếu có <input> một văn bản được nhập bởi khách truy cập, thì văn bản đó sẽ bị xóa. Và như thế.

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

6. outerHTML: HTML đầy đủ của phần tử

Các thuộc tính outerHTML có chứa code HTML đầy đủ các nguyên tố này. Điều đó giống như innerHTML cộng với phần tử chính nó.

Đâ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>

Hãy lưu ý: không giống như innerHTML, viết để outerHTML không thay đổi phần tử. Thay vào đó, nó thay thế nó trong DOM.

Ừ, nghe có vẻ lạ, và thật lạ, đó là lý do tại sao chúng ta ghi chú riêng về nó ở đây. Hãy xem.

Hãy xem xét ví dụ:

<div>Hello, world!</div>

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

  // replace div.outerHTML with <p>...</p>
  div.outerHTML = '<p>A new element</p>'; // (*)

  // Wow! 'div' is still the same!
  alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>

Trông thật kỳ quặc phải không?

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

Việc outerHTML gán 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 là biến ‘div’), nhưng xóa nó khỏi DOM và chèn HTML mới vào vị trí của nó.

Vì vậy, những gì đã xảy ra div.outerHTML=…là:

  • div đã bị xóa khỏi tài liệu.
  • Một đoạn HTML khác <p>A new element</p>đã được chèn vào vị trí của nó.
  • div vẫn còn nguyên giá trị cũ. 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: sửa đổi div.outerHTMLvà sau đó tiếp tục làm việc div như thể nó có nội dung mới trong đó. Điều như vậy là đúng cho innerHTML, nhưng không phải cho outerHTML.

Chúng ta có thể viết thư elem.outerHTML, nhưng nên nhớ rằng nó không thay đổi phần tử mà chúng ta đang viết (‘elem’). Nó đặt HTML mới vào vị trí của nó. Chúng ta có thể nhận được tham chiếu đến các phần tử mới bằng cách truy vấn DOM.

7. nodeValue / data: nội dung nút văn bản

Các thuộc tính innerHTML chỉ có giá trị cho các nút phần tử.

Các loại nút khác, chẳng hạn như các nút văn bản, có thuộc tính nodeValue và đối tác của chúng data. Hai cái này gần như giống nhau để sử dụng thực tế, chỉ có sự khác biệt nhỏ về thông số kỹ thuật. Vì vậy, chúng ta sẽ sử dụng data, vì nó ngắn hơn.

Ví dụ về cách đọc nội dung của nút văn bản và nhận xét:

<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ể hình dung lý do để đọc hoặc sửa đổi chúng, nhưng tại sao lại là nhận xét(comment)?

Đôi khi các developer nhúng thông tin hoặc hướng dẫn mẫu vào HTML trong đó, 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 được nhúng.

8. textContent: văn bản thuần túy

Các textContent cung cấp quyền truy cập vào các nội dung bên trong các phần tử: chỉ có văn bản, trừ đi tất cả <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 có thể thấy, chỉ có văn bản được trả lại, như thể tất cả <tags>đã bị cắt bỏ, nhưng văn bản trong chúng vẫn còn.

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

Viết tới 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 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ó “dưới dạng HTML”, với tất cả các thẻ HTML.
  • Với việc textContent chúng ta sẽ chèn nó “dưới dạng văn bản”, tất cả các ký hiệu được xử lý theo nghĩa đen.

So sánh hai:

<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>
  1. Đầu tiên <div> có tên “là HTML”: tất cả các thẻ đều trở thành thẻ, vì vậy chúng ta thấy tên in đậm.
  2. Thứ hai <div> có tên “dưới dạng văn bản”, vì vậy chúng ta thấy theo nghĩa đen <b>Winnie-the-pooh!</b>.

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 có HTML không mong muốn trong trang web của mình. Một nhiệm vụ để textContent thực hiện chính xác điều đó.

9. Thuộc tính “hidden”

Thuộc tính “hidden” và thuộc tính DOM chỉ đị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 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 hơn để viết.

Đâ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à các thuộc tính phụ thuộc vào lớp:

  • value- giá trị cho<input>, <select> and <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 tiêu chuẩn đều có thuộc tính DOM tương ứng và chúng ta có thể truy cập nó như vậy.

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

Hoặc nếu chúng ta muốn tải chúng nhanh hoặc quan tâm đến thông số kỹ thuật cụ thể của trình duyệt – chúng ta luôn có thể 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á “thuộc tính DOM” trong tab Phần tử của công cụ dành cho nhà phát triển trình duyệt.

11. Tóm lược

Mỗi nút DOM thuộc về một lớp nhất định. Các lớp 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 sự kế thừa.

Các thuộc tính chính của nút DOM là:

nodeType

Chúng ta có thể sử dụng nó để xem một nút là văn bản hay nút phần tử. Nó có một giá trị số: 1 cho các phần tử, 3 cho các nút văn bản và một số giá trị 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ẻ (viết 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ử. Hoạt động ghi vào elem.outerHTML không elem tự động chạm vào . Thay vào đó, nó được 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 cái này gần như giống nhau, thường thì chúng ta sử dụng data. Có thể được sửa đổi.

textContent

Văn bản bên trong phần tử: HTML trừ đi tất cả <tags>. Viết vào nó sẽ đặt văn bản vào bên trong 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 một cách an toàn và bảo vệ khỏi các chèn HTML không mong muốn.

hidden

Khi được đặt thành true, tương tự 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, typetrong 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 đều 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.

Nguồn và tài liệu tiếng anh tham khảo:

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

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!