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

Ilya Grigorik
Ilya Grigorik

Ngày xuất bản: 31 tháng 12 năm 2013

JavaScript cho phép chúng ta sửa đổi hầu hết 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 việc tạo DOM và trì hoãn khi trang được hiển thị. Để mang lại hiệu suất tối ưu, hãy tạo JavaScript không đồng bộ và loại bỏ mọi JavaScript không cần thiết khỏi đường dẫn kết xuất quan trọng.

  • 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 ta thay đổi hầu hết mọi khía cạnh về cách hoạt động của trang: chúng ta 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 ta có thể sửa đổi các thuộc tính CSSOM của từng phần tử; chúng ta có thể xử lý dữ liệu đầu vào của người dùng; và nhiều tính năng khác. Để minh hoạ điều này, hãy xem điều gì sẽ xảy ra khi thay đổi ví dụ "Hello World" trước đó để thêm một tập lệnh ngắn cùng dòng:

<!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 vẫn ở đó trong DOM. Sau đó, khi có tệp tham chiếu, chúng ta có thể thay đổi văn bản của tệp đó (thông qua .textContent) và thậm chí ghi đè thuộc tính kiểu hiển thị được tính toán của tệp đó từ "none" (không có) thành "inline" (nội tuyến). Bây giờ, trang của chúng ta sẽ hiển thị thông báo "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, thêm 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 ta có thể chỉ là một tệp JavaScript lớn tạo và tạo kiểu cho các phần tử lần lượt. Mặc dù cách này sẽ hoạt động, 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 và nối phần tử vào phần nội dung.

Bản xem trước của một trang hiển thị trên thiết bị di động.

Với nội dung đó, chúng ta đã sửa đổi nội dung và kiểu CSS của một nút DOM hiện có, đồ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 sẽ không giành được bất kỳ giải thưởng thiết kế nào, nhưng trang này minh hoạ sức mạnh và tính linh hoạt mà JavaScript mang lại.

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

Trước tiên, hãy lưu ý rằng trong ví dụ trước, tập lệnh nội tuyến của chúng ta nằm gần cuối trang. Tại sao? Bạn nên tự thử, 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 không thành công và báo lỗi 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ề null. Điều này minh hoạ một thuộc tính quan trọng: tập lệnh của chúng ta được thực thi tại chính điểm được chèn vào tài liệu. Khi gặp thẻ tập lệnh, trình phân tích cú pháp HTML sẽ tạm dừng quá trình tạo DOM và chuyển 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 và tiếp tục tạo 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 này trong trang vì các phần tử đó chưa được xử lý! Hoặc nói một cách khác: việc thực thi tập lệnh nội tuyến sẽ chặn quá trình tạo DOM, đồng thời trì hoãn quá trình kết xuất ban đầu.

Một thuộc tính tinh tế khác của việc đưa tập lệnh vào trang của chúng tôi là chúng có thể đọc và sửa đổi không chỉ DOM mà còn 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 có thành nội tuyến. Kết quả cuối cùng? Bây giờ, chúng ta có một tình huống tương tranh.

Điều gì sẽ xảy ra 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? Câu trả lời này không tốt 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à tạo CSSOM.

Tóm lại, JavaScript giới thiệu nhiều phần phụ thuộc mới giữa DOM, CSSOM và quá trình thực thi JavaScript. Điều này có thể khiến trình duyệt bị 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 thẻ tập lệnh, quá trình tạo DOM sẽ tạm dừng cho đến khi tập lệnh thực thi xong.
  • 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.

Ở một mức độ lớn, "tối ưu hoá đường dẫn kết xuất quan trọng" đề cập đến 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 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 quyền kiểm soát cho môi trường thời gian chạy JavaScript và cho phép tập lệnh thực thi trước khi tiếp tục tạo DOM. Chúng ta đã thấy điều này trong ví dụ trước về tập lệnh nội tuyến. Trên thực tế, tập lệnh cùng dòng luôn 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 tập lệnh.

Còn các tập lệnh được đưa vào bằng thẻ tập lệnh thì sao? Lấy ví dụ trước và trích xuất mã vào một tệp riêng:

<!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 ta sử dụng thẻ <script> hay đoạn mã JavaScript nội tuyến, bạn cũng sẽ thấy cả hai đều hoạt động theo cách tương tự. 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. Việc này có thể làm trễ hàng chục đến hàng nghìn mili giây đối với đường dẫn kết xuất quan trọng.

Theo mặc định, tất cả JavaScript đều bị trình phân tích cú pháp chặn. Vì trình duyệt không biết tập lệnh dự định làm gì trên trang, nên trình duyệt sẽ giả định trường hợp xấu nhất và chặn trình phân tích cú pháp. Một tín hiệu cho trình duyệt rằng tập lệnh không cần được thực thi tại chính xác điểm được tham chiếu cho phép trình duyệt tiếp tục tạo DOM và cho phép tập lệnh thực thi khi đã 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.

Để làm được việc này, thuộc tính async được thêm vào phần tử <script>:

<!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ừ khoá không đồng bộ vào thẻ tập lệnh sẽ yêu cầu trình duyệt không chặn việc tạo 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.

Phản hồi