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

Ilya Grigorik
Ilya Grigorik

Ngày xuất bản: ngày 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 trải nghiệm tối ưu hiệu suất, làm cho JavaScript của bạn 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 gần như mọi khía cạnh của 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 các 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 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 xuất hiện trong cây kết xuất, nhưng vẫn có 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ị đã tính từ "none" thành "nội tuyến". Giờ đây, trang của chúng ta sẽ hiển thị thông báo "Hello tương tác học sinh!".

  • 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ẽ hiệu quả nhưng trên thực tế, việc sử dụng HTML và CSS 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à thêm phần tử đó vào phần nội dung.

Bản xem trước của một trang được 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 ta sẽ không giành được giải thưởng thiết kế nào, nhưng nó minh hoạ sức mạnh và tính linh hoạt mà JavaScript mang lại cho chúng ta.

Tuy nhiên, mặc dù JavaScript mang lại cho chúng ta nhiều quyền lực, nhưng nó cũng tạo ra nhiều giới hạn khác về cách và thời điểm hiển thị trang.

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 tôi nằm ở gần cuối trang. Tại sao? Bạn nên tự mình 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à phàn nàn rằng 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 sẽ không thể tìm thấy bất kỳ phần tử nào trong trang sau này vì các phần tử đó chưa được xử lý! Hay nói cách khác một chút là: việc thực thi tập lệnh cùng dòng sẽ chặn quá trình xây dựng DOM, điều này cũng làm chậm quá trình hiển thị 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 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à việc 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.

Trình phân tích cú pháp chặn 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 xây dựng 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 ví dụ trước về tập lệnh nội tuyến. Trên thực tế, các 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 các tập lệnh đó.

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

Liệu chúng tôi có sử dụng <script> hay không hoặc một đoạn mã JavaScript cùng dòng, bạn sẽ mong muốn cả hai hành vi như 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 có tệp JavaScript bên ngoài, trình duyệt phải tạm dừng để đợi tập lệnh được tìm nạp từ đĩa, bộ nhớ đệm hoặc máy chủ từ xa có thể thêm độ trễ từ hàng chục đến hàng nghìn mili giây vào quá trình kết xuất quan trọng đường dẫn.

Theo mặc định, tất cả JavaScript đều bị trình phân tích cú pháp chặn. Do trình duyệt không biết tập lệnh đang dự định thực hiện thao tác gì trên trang nên trình duyệt 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 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