Hiểu domcontentloaded qua một ví dụ với 100.000 dữ liệu

Nội dung bài viết

Domcontentloaded là gì? Với một ví dụ với 100.000 dữ liệu cần hiện thị trên web thì bạn sẽ phân biệt và hiểu Domcontentloaded Event, đừng lo lắng. Nếu bạn nào có nghĩ rằng, thực tế có ai mà trả về cho client 100K items. Giờ người ta làm phân trang hết rồi. Đúng đa số là vậy, còn sai là bạn chưa gặp và sẽ gặp mà thôi. Lý do, một thằng không muốn những người khác làm phiền nhiều lần. Hy vọng có ai hiểu không???


Đầu tiên bạn là một lập trình viên vậy cho tôi hỏi bạn đã nắm hết tất cả nhưng event trong document chưa? Cụ thể là load, readystatechange, beforeunload, unload... Nếu chưa, thì bạn đã bỏ sót khá quan trọng trong vấn đề lập trình về DOM cũng như rất ít quan tâm đến hiệu suất của một sites. kết luận là bạn chưa là một dev tốt. Thật mà thẳng. 

Thứ hai, bài viết này tập trung mục tiêu làm sao cho bạn hiểu về Domcontentloaded Event. Một sự kiện khá quan trọng, và gây nhầm lẫn nhất. 

Thứ ba, có rất nhiều bài viết liên quan đến "Sự kiện domcontentloaded", và tôi cũng đã đọc xem thử họ đã làm tốt hay chưa. Nhưng thực sự tôi đọc xong là không hiểu gì hết luôn. Rõ chán. Cho nên tôi quyết định viết một bài viết và tham khảo rất nhiều nơi, và cho ra bài viết này. Và tôi khuyên, nếu có đọc thì nên đọc ở những bài viết như "DOMContentLoaded W3Schools", hay tại MDN

Thứ tư, xin vui lòng tắt facebook và ngồi ngay ngắn để tập trung vào bài viết. Dự là sẽ phân tích rất khó.


Domcontentloaded là gì?


The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. - MDN


Dịch: Sự kiện DOMContentLoaded kích hoạt khi tài liệu HTML ban đầu đã được tải và phân tích cú pháp hoàn toàn mà không cần đợi những file như stylesheets, images và subframes cho đến khi hoàn tất tải file đó. OK chưa? Hiểu chưa, hiểu rồi thì close được rồi, còn không ngồi đó. Tiếp tục đến phần giải thích của tôi.


Sự kiện domcontentloaded


Trước hết xem ví dụ được trích ra từ MDN:

window.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM fully loaded and parsed');
});

`DOM fully loaded and parsed` bạn cần lưu ý câu này. Nghĩa là sự kiện domcontentloaded được kích hoạt thì đồng nghĩa với 2 việc `loaded and parsed`. Vậy ta đi từ hai khái niệm này trước. 

### loaded hay tải xuống hay tải... 

Hiểu nhanh và gọn mấy từ này diễn đạt cùng một nghĩa, đó là quá trình trình duyệt tải tài nguyên xuống cục bộ. hết. 


### parsed hay là phân tích cú pháp 

parsed hay còn gọi là phân tích cú pháp có nghĩa là chuyển đổi một phần tử thành một dạng khác theo một cách nhất định. Chẳng hạn như phân tích cú pháp html. Trước hết, cần phải thấy rõ rằng biểu hiện của html tải xuống trình duyệt là một tập tin chứa chuỗi ký tự. Trình duyệt đọc chuỗi trong tệp html vào bộ nhớ, biên dịch chuỗi theo quy tắc html và chuyển chuỗi thành một cấu trúc dữ liệu khác dễ diễn đạt. Để không nói ngoa thì xem một ví dụ:

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>Giair thich viec phan tich cú pháp</title>
  <link rel="stylesheet" href="./index.css" />
</head>
<body>
  <div id="div1"></div>
  <link rel="stylesheet" href="./c1.css" />
  <link rel="stylesheet" href="./c3.css" />
  <script src="/xxxxxx.com/file1.js"></script>
  <link rel="stylesheet" href="./c4.css" />
  <div id="div2"></div>
</body>
</html>

Trình duyệt sẽ biên dịch tệp html này thành một cấu trúc tương tự như sau: Hình ảnh bổ sung sau Đó là khái niệm parsed. OK??? 


Ví dụ về domcontentloaded - Trang cần hiển thị 100.000 phần dữ liệu. Làm thế nào để đạt được điều này?


Mục tiêu là làm cho mượt mà và không có kéo thời gian dây thun, không nữa người dùng người ta bỏ đi. 

Cách 1 - Cách củ chuối của những bạn thiếu kinh nghiệm. 

Mẹo nhỏ: Cách phân biệt kinh nghiệm và chém gió khi xem code của lập trình viên.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Test hiệu suất 100.000 data</title>
</head>
<body>
    <ul id="root">

    </ul>
    <script>
        function createOneHundredThousandData(){
            let arr = [];
            for(let i=0;i<100000;i++){
              arr.push({
                imgUrl:'https://cf.shopee.vn/file/2c1e846120cefebd49e8ba45acd2d100',
                key:i
              })
            }
            return arr;
        }
        var beginTime = performance.now();
        console.log('Bắt đầu',beginTime);
        /* console.time('bắt đầu chạy thử test') */
        
        let h = [];
        let data = createOneHundredThousandData()
        // phương phá củ chuối với for
        for(let i =0;i<data.length;i++){
            h.push('<li>' + '<img src="'+ data[i].imgUrl +'" \/>'+ 'current index ' + data[i].key + '<\/li>');
        }
        // Nếu ngon hơn thì chơi map cho nó chuyên nghiệp
        
        document.getElementById('root').innerHTML = h.join('');
        document.addEventListener('DOMContentLoaded',function(){
          var endTime = performance.now();
          console.log('DOMContentLoaded endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('DOMContentLoaded render 100000 items mất hết ' + total + ' Giây');
          /* console.timeEnd('bắt đầu chạy thử test') */
        });
        window.onload = function(){
          var endTime = performance.now();
          console.log('window.onload endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('window.onload render 100000 items mất hết ' + total + ' Giây');  
        }
    </script>
</body>
</html>

Xem hình ảnh khi check xong, tôi đang sử dụng Chrome mới mất, phiên bản Version 87.0.4280.67 (Official Build) (x86_64).

Nói cách khác, việc render chứa 100.000 dữ liệu và mỗi phần dữ liệu chỉ là sự kết hợp đơn giản giữa hình ảnh và văn bản, mất gần 11 giây. Trước khi render trang hoàn tất, người ta ước tính rằng người dùng đã mất kiên nhẫn và đóng trang. Rõ ràng, phương pháp truyền thống chắc chắn không đủ tiêu chuẩn.


Cách 2: Cách thơm lừng mà tôi đã từng giới thiệu trong bài viết về "Muốn tối ưu hoá hãy dùng Promise"

Vợ gọi xuống ăn cơm, để đây viết sau cách thứ 2 và thứ 3... 

Update tiếp theo, có mấy bạn hỏi ăn cơm gì lâu vậy, có trả bài không??? Há há... Con còn nhỏ, phải lựa thời gian trả bài mới được, chứ trả lung tung không được nhá. Bây giờ chúng ta đi cách tiếp theo , mẹo truyền thống của mấy chế kinh nghiệm đó là chạy mỗi vòng là 100 items và sử dụng setTimeout() một kỹ thuật lâu năm, nhưng có thể bạn chưa biết.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Test hiệu suất 100.000 data</title>
</head>
<body>
    <ul id="root">

    </ul>
    <script>
        function createOneHundredThousandData(){
            let arr = [];
            for(let i=0;i<100000;i++){
              arr.push({
                imgUrl:'https://cf.shopee.vn/file/2c1e846120cefebd49e8ba45acd2d100',
                key:i
              })
            }
            return arr;
        }
        var beginTime = performance.now();
        console.log('Bắt đầu',beginTime);
        /* console.time('bắt đầu chạy thử test') */
        
        let h = [];
        let data = createOneHundredThousandData()
        // lấy 100 items đầu tiên
        let firstScreenData = data.splice(0,100); // Sử dụng splice
        for(let i=0;i<100;i++){
          let li = document.createElement('li');
          let img = document.createElement('img');
          img.src  = firstScreenData[i].imgUrl;
          li.appendChild(img);
          let text = document.createTextNode(firstScreenData[i].key);
          li.appendChild(text);
          document.getElementById('root').appendChild(li);
        }  
        
        // setTimeout callback sẽ được thực thi
        
        setTimeout(()=>{
          function renderHundred(n){
            // console.log('n=',n);
            // Mỗi lần hiện thị 100 items
            let partialData = data.splice(0,100);
            for(let i=0;i<100 && partialData.length>0;i++){
              let li = document.createElement('li');
              let img = document.createElement('img');
              img.src  = partialData[i].imgUrl;
              li.appendChild(img);
              let text = document.createTextNode(partialData[i].key);
              // console.log('partialData[i].key',partialData[i].key);
              li.appendChild(text);
              document.getElementById('root').appendChild(li);
            }            
            if(n){
              setTimeout(()=>{
                renderHundred(n-1);
              },50)
            }       
          }
          renderHundred(999);// chạy 1000 loop mới mỗi loop là 100 items hiểu không
        },1000); 

        
        //document.getElementById('root').innerHTML = h.join('');
        document.addEventListener('DOMContentLoaded',function(){
          var endTime = performance.now();
          console.log('DOMContentLoaded endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('DOMContentLoaded render 100000 items mất hết ' + total + ' Giây');
          /* console.timeEnd('bắt đầu chạy thử test') */
        });
        window.onload = function(){
          var endTime = performance.now();
          console.log('window.onload endTime',endTime);
          var total = ((endTime - beginTime)/1000).toFixed(5);        
          console.log('window.onload render 100000 items mất hết ' + total + ' Giây');  
        }
    </script>
</body>
</html>

Đây là hình ảnh chạy sau khi tôi sử dụng cách thứ 2. xem và đối chiếu với cách 1, nó nhanh gấp vạn lần =]]


Đó các bạn xem, nó nhanh chưa. Mà nói tôi lừa :(.... Cách 3 nữa không?

Có thể bạn đã bị missing