Thêm tính tương tác với JavaScript

Ilya Grigorik
Ilya Grigorik

JavaScript cho phép chúng tôi sửa đổi mọi khía cạnh của trang: nội dung, kiểu và phản hồi của trang đối với hoạt động tương tác của người dùng. Tuy nhiên, JavaScript cũng có thể chặn quá trình tạo DOM và trì hoãn việc hiển thị trang. Để mang lại hiệu suất tối ưu, hãy đặt JavaScript ở chế độ không đồng bộ và loại bỏ mọi JavaScript không cần thiết khỏi đường dẫn hiển thị quan trọng.

Tóm tắt

  • JavaScript có thể truy vấn và sửa đổi DOM và CSSOM.
  • Các khối thực thi JavaScript trên CSSOM.
  • JavaScript chặn xây dựng DOM trừ khi được khai báo rõ ràng là không đồng bộ.

JavaScript là một ngôn ngữ động chạy trong trình duyệt và cho phép chúng tôi thay đổi mọi khía cạnh về cách hoạt động của trang: chúng tôi có thể sửa đổi nội dung bằng cách thêm và xoá các phần tử khỏi cây DOM; chúng tôi có thể sửa đổi thuộc tính CSSOM của mỗi phần tử; chúng tôi có thể xử lý hoạt động đầu vào của người dùng; và nhiều hơn thế nữa. Để minh hoạ điều này, hãy củng cố ví dụ về "Hello World" ("Xin chào thế giới") trước đó bằng một tập lệnh cùng dòng đơn giản:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

Thử nào

  • JavaScript cho phép chúng ta truy cập vào DOM và lấy tham chiếu đến nút span ẩn; nút này có thể không hiển thị trong cây hiển thị, nhưng nó vẫn ở đó trong DOM. Sau đó, khi có tham chiếu, chúng ta có thể thay đổi văn bản của tham chiếu (thông qua .textContent) và thậm chí ghi đè thuộc tính kiểu hiển thị được tính toán từ "none" thành "inline". Giờ đây, trang của chúng tôi hiển thị nội dung "Xin chào các học viên tương tác!".

  • JavaScript cũng cho phép chúng ta tạo, tạo kiểu, nối và xoá các phần tử mới trong DOM. Về mặt kỹ thuật, toàn bộ trang của chúng tôi có thể chỉ là một tệp JavaScript lớn dùng để tạo và tạo kiểu cho từng phần tử. Mặc dù cách này sẽ hiệu quả, nhưng trong thực tế, việc sử dụng HTML và CSS sẽ dễ dàng hơn nhiều. Trong phần thứ hai của hàm JavaScript, chúng ta tạo một phần tử div mới, đặt nội dung văn bản, tạo kiểu cho phần tử và thêm phần tử đó vào phần nội dung.

bản xem trước trang

Cùng với đó, chúng tôi đã sửa đổi nội dung và kiểu CSS của nút DOM hiện tại, đồng thời thêm một nút hoàn toàn mới vào tài liệu. Trang của chúng tôi không giành được giải thưởng nào về thiết kế nhưng minh hoạ sức mạnh và tính linh hoạt mà JavaScript mang lại cho chúng tôi.

Tuy nhiên, mặc dù JavaScript mang lại cho chúng tôi rất nhiều sức mạnh, nhưng nó tạo ra rất nhiều hạn chế bổ sung về cách thức và thời điểm hiển thị trang.

Trước tiên, hãy lưu ý rằng trong ví dụ trên, tập lệnh cùng dòng của chúng ta nằm ở gần cuối trang. Tại sao? Bạn nên thử tự làm, nhưng nếu chúng ta di chuyển tập lệnh lên trên phần tử span, bạn sẽ nhận thấy tập lệnh bị lỗi và nhận rằng tập lệnh không tìm thấy tham chiếu đến bất kỳ phần tử span nào trong tài liệu; tức là getElementsByTagName('span') trả về giá trị null. Điều này thể hiện một thuộc tính quan trọng: tập lệnh của chúng ta được thực thi tại đúng điểm mà tập lệnh được chèn vào tài liệu. Khi trình phân tích cú pháp HTML gặp phải một thẻ tập lệnh, nó sẽ tạm dừng quá trình tạo DOM và mang lại quyền kiểm soát cho công cụ JavaScript; sau khi công cụ JavaScript chạy xong, trình duyệt sẽ tiếp tục từ nơi đã dừng lại và tiếp tục xây dựng DOM.

Nói cách khác, khối tập lệnh của chúng ta không thể tìm thấy bất kỳ phần tử nào sau đó trong trang vì chúng chưa được xử lý! Hoặc, đặt hơi khác một chút: việc thực thi tập lệnh cùng dòng của chúng ta sẽ chặn hoạt động tạo DOM, điều này cũng làm chậm quá trình kết xuất ban đầu.

Một đặc điểm tinh vi khác của việc đưa tập lệnh vào trang là các tập lệnh có thể đọc và sửa đổi không chỉ DOM mà còn có thể sửa đổi các thuộc tính CSSOM. Trên thực tế, đó chính xác là những gì chúng ta đang làm trong ví dụ khi thay đổi thuộc tính hiển thị của phần tử span từ không sang cùng dòng. Kết quả cuối cùng là gì? Chúng ta hiện có một điều kiện tranh đấu.

Nếu trình duyệt chưa hoàn tất việc tải xuống và tạo CSSOM khi chúng ta muốn chạy tập lệnh thì sao? Câu trả lời rất đơn giản và không hiệu quả cho hiệu suất: trình duyệt trì hoãn việc thực thi tập lệnh và xây dựng DOM cho đến khi hoàn tất việc tải xuống và xây dựng CSSOM.

Tóm lại, JavaScript tạo ra nhiều phần phụ thuộc mới giữa DOM, CSSOM và việc thực thi JavaScript. Điều này có thể khiến trình duyệt chậm trễ đáng kể trong quá trình xử lý và hiển thị trang trên màn hình:

  • Vị trí của tập lệnh trong tài liệu rất quan trọng.
  • Khi trình duyệt gặp một thẻ tập lệnh, quá trình tạo DOM sẽ tạm dừng cho đến khi tập lệnh hoàn tất việc thực thi.
  • JavaScript có thể truy vấn và sửa đổi DOM và CSSOM.
  • Quá trình thực thi JavaScript sẽ tạm dừng cho đến khi CSSOM sẵn sàng.

Nhìn chung, việc "tối ưu hoá đường dẫn hiển thị quan trọng" đồng nghĩa với việc hiểu và tối ưu hoá biểu đồ phần phụ thuộc giữa HTML, CSS và JavaScript.

Chặn trình phân tích cú pháp so với JavaScript không đồng bộ

Theo mặc định, quá trình thực thi JavaScript là "chặn trình phân tích cú pháp": khi trình duyệt gặp một tập lệnh trong tài liệu, trình duyệt phải tạm dừng quá trình tạo DOM, chuyển giao quyền kiểm soát cho thời gian chạy JavaScript và cho phép tập lệnh thực thi trước khi tiếp tục xây dựng DOM. Chúng ta đã thấy điều này trong thực tế với một tập lệnh cùng dòng trong ví dụ trước. Trên thực tế, các tập lệnh cùng dòng luôn là lệnh chặn trình phân tích cú pháp, trừ phi bạn viết thêm mã để trì hoãn việc thực thi các tập lệnh đó.

Còn các tập lệnh được bao gồm thông qua thẻ tập lệnh thì sao? Hãy xem ví dụ trước và trích xuất mã vào một tệp riêng biệt:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

Thử nào

Cho dù chúng tôi sử dụng thẻ <script> hay đoạn mã JavaScript cùng dòng, bạn đều muốn cả hai hoạt động giống nhau. Trong cả hai trường hợp, trình duyệt sẽ tạm dừng và thực thi tập lệnh trước khi có thể xử lý phần còn lại của tài liệu. Tuy nhiên, trong trường hợp tệp JavaScript bên ngoài, trình duyệt phải tạm dừng để chờ tìm nạp tập lệnh từ ổ đĩa, bộ nhớ đệm hoặc máy chủ từ xa. Quá trình này có thể trì hoãn hàng chục đến hàng nghìn mili giây cho đường dẫn hiển thị quan trọng.

Theo mặc định, tất cả JavaScript là chặn trình phân tích cú pháp. Do không biết tập lệnh đang định làm gì trên trang nên trình duyệt sẽ giả định trường hợp xấu nhất là chặn trình phân tích cú pháp. Một tín hiệu để trình duyệt biết rằng tập lệnh không cần được thực thi tại điểm được tham chiếu đến, cho phép trình duyệt tiếp tục xây dựng DOM và cho phép tập lệnh thực thi khi tập lệnh đã sẵn sàng; ví dụ: sau khi tệp được tìm nạp từ bộ nhớ đệm hoặc máy chủ từ xa.

Để đạt được điều này, chúng ta đánh dấu tập lệnh là async:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Thử nào

Việc thêm từ khóa không đồng bộ vào thẻ tập lệnh sẽ yêu cầu trình duyệt không chặn xây dựng DOM trong khi chờ tập lệnh có sẵn, điều này có thể cải thiện đáng kể hiệu suất.

Ý kiến phản hồi