Sửa đổi DOM là chìa khóa để tạo các trang “live”(sống động, thực hơn). Vì vậy hôm nay, cafedev chia sẻ cho ace về cách sửa đổi Document để tạo trang web động hơn.

Ở đây, chúng ta sẽ xem cách tạo các phần tử mới “nhanh chóng” và sửa đổi nội dung trang hiện có.

1. Ví dụ: hiển thị một tin nhắn

Hãy chứng minh bằng cách sử dụng một ví dụ. Chúng ta sẽ thêm một thông báo trên trang trông đẹp hơn alert.

Đây là cách nó sẽ trông như thế nào:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="alert">
  <strong>Hi there!</strong> You've read an important message.
</div>

Đó là ví dụ HTML. Bây giờ chúng ta hãy tạo tương tự div với JavaScript (giả sử rằng các kiểu đã có trong HTML / CSS).

2. Tạo một phần tử

Để tạo các nút DOM, có hai phương pháp:

document.createElement(tag)

Tạo một nút phần tử mới với thẻ đã cho:

let div = document.createElement(‘div’);

document.createTextNode(text)

Tạo một nút văn bản mới với văn bản đã cho:

let textNode = document.createTextNode(‘Here I am’);

Hầu hết thời gian chúng ta cần tạo các nút phần tử, chẳng hạn như nút div cho thông báo.

2.1. Tạo tin nhắn

Tạo div thông báo thực hiện 3 bước:

// 1. Create <div> element
let div = document.createElement('div');

// 2. Set its class to "alert"
div.className = "alert";

// 3. Fill it with the content
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

Chúng ta đã tạo phần tử. Nhưng hiện tại nó chỉ nằm trong một biến có tên div, chưa có trong trang. Vì vậy, chúng ta không thể nhìn thấy nó.

2.2. Phương thức chèn

Để div hiển thị, chúng ta cần chèn nó vào đâu đó document. Ví dụ, thành phần tử <body>

Có một phương pháp đặc biệt thêm điều đó: document.body.append(div).

Đây là code đầy đủ:

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

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert";
  div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

  document.body.append(div);
</script>

Ở đây chúng ta gọi append vào document.body, nhưng chúng ta có thể gọi phương thức append trên bất kỳ phần tử khác, để đưa phần tử khác vào nó. Ví dụ, chúng ta có thể nối một cái gì đó vào <div> bằng cách gọi div.append(anotherElement).

Dưới đây là nhiều phương pháp chèn hơn, chúng chỉ định các vị trí khác nhau để chèn:

  • node.append(…nodes or strings)- append nút hoặc các chuỗi vào cuối của node,
  • node.prepend(…nodes or strings)- chèn nút hoặc các chuỗi ở đầu của node,
  • node.before(…nodes or strings)–- chèn các nút hoặc chuỗi trước node ,
  • node.after(…nodes or strings)–- chèn các nút hoặc chuỗi sau node ,
  • node.replaceWith(…nodes or strings)–- thay thế node bằng các nút hoặc chuỗi đã cho.

Hãy xem chúng hoạt động.

Dưới đây là một ví dụ về việc sử dụng các phương pháp này để thêm các mục vào danh sách và văn bản trước / sau nó:

<ol id="ol">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  ol.before('before'); // insert string "before" before <ol>
  ol.after('after'); // insert string "after" after <ol>

  let liFirst = document.createElement('li');
  liFirst.innerHTML = 'prepend';
  ol.prepend(liFirst); // insert liFirst at the beginning of <ol>

  let liLast = document.createElement('li');
  liLast.innerHTML = 'append';
  ol.append(liLast); // insert liLast at the end of <ol>
</script>

Dưới đây là một hình ảnh trực quan về những gì các phương thức:

Vì vậy, danh sách cuối cùng sẽ là:

before
<ol id="ol">
  <li>prepend</li>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>append</li>
</ol>
after

Như đã nói, các phương thức này có thể chèn nhiều nút và đoạn văn bản trong một lệnh gọi.

Ví dụ, ở đây một chuỗi và một phần tử được chèn:

<div id="div"></div>
<script>
  div.before('<p>Hello</p>', document.createElement('hr'));
</script>

Hãy lưu ý: các văn bản được chèn “dưới dạng văn bản”, không phải “dưới dạng HTML”, với các phần tử như <, >.

Vì vậy, HTML cuối cùng là:

<p>Hello</p>
<hr>
<div id="div"></div>

Nói cách khác, các chuỗi được chèn vào một cách an toàn, giống như elem.textContent.

Vì vậy, các phương phức này chỉ có thể được sử dụng để chèn các nút DOM hoặc các đoạn văn bản.

Nhưng điều gì sẽ xảy ra nếu chúng ta muốn chèn một chuỗi HTML “dưới dạng html”, với tất cả các thẻ và nội dung hoạt động, theo cách tương tự như elem.innerHTML?

2.3. insertAdjacentHTML / Text / Element

Cho rằng chúng ta có thể sử dụng khác, khá linh hoạt phương thức: elem.insertAdjacentHTML(where, html).

Tham số đầu tiên là một từ code, chỉ định vị trí cần chèn liên quan elem. Phải là một trong những điều sau:

  • “beforebegin”- chèn html ngay trước đó elem,
  • “afterbegin”- chèn html vào elem, ở đầu,
  • “beforeend”- chèn htm vào elem, ở cuối,
  • “afterend”- chèn html ngay sau đó elem.

Tham số thứ hai là một chuỗi HTML, được chèn “dưới dạng HTML”.

Ví dụ:

<div id="div"></div>
<script>
  div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
  div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>

…Sẽ dẫn đến:

<p>Hello</p>
<div id="div"></div>
<p>Bye</p>

Đó là cách chúng ta có thể nối HTML tùy ý vào trang.

Đây là hình ảnh của các biến thể chèn:

Chúng ta có thể dễ dàng nhận thấy những điểm tương đồng giữa bức tranh này và bức ảnh trước đó. Các điểm chèn thực sự giống nhau, nhưng phương thức này chèn HTML.

Phương thức này có hai anh em:

  • elem.insertAdjacentText(where, text)- cú pháp giống nhau, nhưng một chuỗi text được chèn “dưới dạng văn bản” thay vì HTML,
  • elem.insertAdjacentElement(where, elem) – cú pháp giống nhau, nhưng chèn một phần tử.

Chúng tồn tại chủ yếu để làm cho cú pháp “thống nhất”. Trong thực tế, chỉ insertAdjacentHTML được sử dụng hầu hết thời gian. Bởi vì đối với các phần tử và văn bản, chúng ta có các phương thức append/prepend/before/after- chúng ngắn hơn để viết và có thể chèn các nút / đoạn văn bản.

Vì vậy, đây là một biến thể thay thế của việc hiển thị thông báo:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  document.body.insertAdjacentHTML("afterbegin", `<div class="alert">
    <strong>Hi there!</strong> You've read an important message.
  </div>`);
</script>

3. Loại bỏ nút

Để loại bỏ một nút, có một phương thức node.remove().

Hãy làm cho thông điệp của chúng ta biến mất sau một giây:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert";
  div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

  document.body.append(div);
  setTimeout(() => div.remove(), 1000);
</script>

Xin lưu ý: nếu chúng ta muốn di chuyển một phần tử đến một nơi khác – không cần phải xóa phần tử đó khỏi phần cũ.

Tất cả các phương pháp chèn tự động loại bỏ nút khỏi vị trí cũ.

<div id="first">First</div>
<div id="second">Second</div>
<script>
  // no need to call remove
  second.after(first); // take #second and after it insert #first
</script>

4. Nhân bản các nút: cloneNode

Làm thế nào để chèn thêm một tin nhắn tương tự?

Chúng ta có thể tạo một hàm và đặt mã ở đó. Nhưng cách thay thế sẽ là sao chép hiện có div và sửa đổi văn bản bên trong nó (nếu cần).

Đôi khi khi chúng ta có một yếu tố lớn, điều đó có thể nhanh hơn và đơn giản hơn.

  • Lệnh gọi elem.cloneNode(true)tạo ra một bản sao “sâu” của phần tử – với tất cả các thuộc tính và thành phần phụ. Nếu chúng ta gọi elem.cloneNode(false), thì bản sao được tạo ra mà không có phần tử con.

Một ví dụ về sao chép tin nhắn:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="alert" id="div">
  <strong>Hi there!</strong> You've read an important message.
</div>

<script>
  let div2 = div.cloneNode(true); // clone the message
  div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone

  div.after(div2); // show the clone after the existing div
</script>

5. DocumentFragment

DocumentFragment là một nút DOM đặc biệt đóng vai trò như một trình bao bọc để di chuyển xung quanh danh sách các nút.

Chúng ta có thể nối các nút khác vào nó, nhưng khi chúng ta chèn nó vào đâu đó, thì nội dung của nó sẽ được chèn vào.

Ví dụ: getListContent bên dưới tạo một phân đoạn với các mục<li>, sau này được chèn vào <ul>:

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

<ul id="ul"></ul>

<script>
function getListContent() {
  let fragment = new DocumentFragment();

  for(let i=1; i<=3; i++) {
    let li = document.createElement('li');
    li.append(i);
    fragment.append(li);
  }

  return fragment;
}

ul.append(getListContent()); // (*)
</script>

Xin lưu ý, ở dòng cuối cùng, (*) chúng ta thêm vào DocumentFragment, nhưng nó “trộn vào”, vì vậy cấu trúc kết quả sẽ là:

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

DocumentFragment hiếm khi được sử dụng một cách rõ ràng. Tại sao lại nối vào một loại nút đặc biệt, nếu thay vào đó chúng ta có thể trả về một mảng các nút? Ví dụ viết lại:

<ul id="ul"></ul>

<script>
function getListContent() {
  let result = [];

  for(let i=1; i<=3; i++) {
    let li = document.createElement('li');
    li.append(i);
    result.push(li);
  }

  return result;
}

ul.append(...getListContent()); // append + "..." operator = friends!
</script>

Chúng ta đề cập DocumentFragment chủ yếu bởi vì có một số khái niệm trên đó.

6. Chèn / xóa phương thức kiểu cũ

Trường cũ

Thông tin này giúp hiểu các tập lệnh cũ, nhưng không cần thiết cho sự phát triển mới.

Ngoài ra còn có các phương thức thao tác DOM “cũ”, tồn tại vì lý do lịch sử.

Những phương thức này có từ thời cổ đại. Ngày nay, không có lý do để sử dụng chúng, như các phương thức hiện đại, chẳng hạn như append, prepend, before, after, remove, replaceWith, là linh hoạt hơn.

Lý do duy nhất chúng ta liệt kê các phương thức này ở đây là bạn có thể tìm thấy chúng trong nhiều tập lệnh cũ:

parentElem.appendChild(node)

Xuất hiện node với tư cách là đứa con cuối cùng của parentElem.

Ví dụ sau thêm một <li>mới vào cuối <ol>:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  let newLi = document.createElement('li');
  newLi.innerHTML = 'Hello, world!';

  list.appendChild(newLi);
</script>

Chèn node trước khi nextSibling vào parentElem.

Đoạn code sau sẽ chèn một danh sách mục mới trước mục thứ hai <li>:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>
<script>
  let newLi = document.createElement('li');
  newLi.innerHTML = 'Hello, world!';

  list.insertBefore(newLi, list.children[1]);
</script>

Để chèn newLi làm phần tử đầu tiên, chúng ta có thể làm như sau:

list.insertBefore(newLi, list.firstChild);

Thay thế oldChild bằng node giữa các con của parentElem.

parentElem.removeChild(node)

Loại bỏ node khỏi parentElem(giả sử node là con của nó).

Ví dụ sau loại bỏ đầu tiên <li>khỏi <ol>:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  let li = list.firstElementChild;
  list.removeChild(li);
</script>

Tất cả các phương thức này trả về nút được chèn / loại bỏ. Nói cách khác, parentElem.appendChild(node) tiện lợi node. Nhưng thông thường giá trị trả về không được sử dụng, chúng ta chỉ chạy phương thức.

7. Một từ về “document.write”

Có một nhiều hơn, phương thức rất cổ xưa của việc thêm một cái gì đó để một trang web: document.write.

Cú pháp:

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

<p>Somewhere in the page...</p>
<script>
  document.write('<b>Hello from JS</b>');
</script>
<p>The end</p>

Lời kêu gọi document.write(html) viết html vào trang “ngay tại đây và ngay bây giờ”. Các chuỗi html có thể được tạo ra tự động, vì vậy nó là loại linh hoạt. Chúng ta có thể sử dụng JavaScript để tạo một trang web chính thức và viết nó.

Phương thức này xuất phát từ thời không có DOM, không có tiêu chuẩn… Thực sự là thời xưa. Nó vẫn sống, bởi vì có những script sử dụng nó.

Trong các tập lệnh hiện đại, chúng ta hiếm khi có thể nhìn thấy nó, vì hạn chế quan trọng sau:

Lệnh gọi document.write chỉ hoạt động khi trang đang tải.

Nếu chúng ta gọi nó sau đó, nội dung tài liệu hiện có sẽ bị xóa.

Ví dụ:

<p>After one second the contents of this page will be replaced...</p>
<script>
  // document.write after 1 second
  // that's after the page loaded, so it erases the existing content
  setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>

Vì vậy, nó không thể sử dụng được ở giai đoạn “sau khi tải”, không giống như các phương pháp DOM khác mà chúng ta đã đề cập ở trên.

Đó là nhược điểm.

Cũng có một mặt trái. Về mặt kỹ thuật, khi nào document.write được gọi trong khi trình duyệt đang đọc (“phân tích cú pháp”) HTML đến và nó viết nội dung nào đó, trình duyệt sẽ sử dụng nó như thể ban đầu ở đó, trong văn bản HTML.

Vì vậy, nó hoạt động cực kỳ nhanh chóng, bởi vì không có sửa đổi DOM . Nó ghi trực tiếp vào văn bản trang, trong khi DOM chưa được xây dựng.

Vì vậy, nếu chúng ta cần thêm nhiều văn bản vào HTML động, và chúng ta đang ở giai đoạn tải trang, và vấn đề tốc độ, nó có thể hữu ích. Nhưng trong thực tế, những yêu cầu này hiếm khi đi kèm với nhau. Và thông thường chúng ta có thể thấy phương pháp này trong các script chỉ vì chúng đã cũ.

8. Tóm lược

  • Các phương pháp tạo các nút mới:
    • document.createElement(tag) – tạo một phần tử với thẻ đã cho,
    • document.createTextNode(value) – tạo một nút văn bản (hiếm khi được sử dụng),
    • elem.cloneNode(deep)- sao chép phần tử, nếu deep==true sau đó với tất cả con cháu.
  • Chèn và xóa:
    • node.append(…nodes or strings)- chèn vào node, ở cuối,
    • node.prepend(…nodes or strings)- chèn vào node, ở đầu,
    • node.before(…nodes or strings)–- chèn ngay trước node,
    • node.after(…nodes or strings)–- chèn ngay sau node,
    • node.replaceWith(…nodes or strings)–- thay thế node.
    • node.remove()–- loại bỏ node.
  • Chuỗi văn bản được chèn “dưới dạng văn bản:
  • Ngoài ra còn có các phương thức cũ “old school”:
    • parent.appendChild(node)
    • parent.insertBefore(node, nextSibling)
    • parent.removeChild(node)
    • parent.replaceChild(newElem, node)
  • Tất cả các phương thức này trả về node.
  • Cho một số HTML vào html, hãy elem.insertAdjacentHTML(where, html) chèn nó tùy thuộc vào giá trị của where:
    • “beforebegin”- chèn html ngay trước elem,
    • “afterbegin”- chèn html vào elem, ở đầu,
    • “beforeend”- chèn htm vào elem, ở cuối,
    • “afterend”- chèn html ngay sau đó elem.
  • Ngoài ra, có những phương thức tương tự elem.insertAdjacentTextvà elem.insertAdjacentElement chèn các chuỗi và phần tử văn bản, nhưng chúng hiếm khi được sử dụng
  • Để nối HTML vào trang trước khi tải xong:
    • document.write(html)
  • Sau khi trang được tải, một cuộc gọi như vậy sẽ xóa tài liệu. Chủ yếu được thấy trong các script cũ.

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!