Cấu trúc cơ bản của trình duyệt
Một trình duyệt web hiện đại bao gồm nhiều thành phần, trong đó công cụ hiển thị (rendering engine) là cốt lõi để chuyển đổi mã nguồn thành hình ảnh trực quan.
Các công cụ hiển thị phổ biến
- Trident: Sử dụng bởi Internet Explorer.
- WebKit: Nền tảng cho Chrome (trước đây) và Safari.
- Gecko: Được FireFox sử dụng.
Ngày nay, hầu hết các trình duyệt hiện đại như Chrome, Edge, Opera đã chuyển sang dùng công cụ dựa trên WebKit hoặc Blink (một fork của WebKit). Firefox vẫn tiếp tục phát triển Gecko.
Quy trình làm việc của trình duyệt
Khi người dùng nhập một địa chỉ URL và nhấn Enter, trình duyệt sẽ trải qua một chuỗi các bước phức tạp để tải và hiển thị nội dung trang web:
- Phân giải tên miền (DNS Lookup).
- Thiết lập kết nối TCP.
- Gửi yêu cầu HTTP.
- Máy chủ xử lý yêu cầu.
- Máy chủ gửi lại phản hồi.
- Đóng kết nối TCP.
- Trình duyệt phân tích và hiển thị nội dung.
Phân tích và hiển thị nội dung trình duyệt
Phân giải tên miền DNS
Khi bạn nhập một URL, trình duyệt sẽ kiểm tra tệp hosts cục bộ để tìm địa chỉ IP tương ứng. Nếu không có, nó sẽ bắt đầu quá trình truy vấn máy chủ DNS. Trình duyệt gửi yêu cầu đến máy chủ DNS cục bộ (thường do nhà cung cấp dịch vụ internet cung cấp). Máy chủ này có thể chuyển tiếp yêu cầu đến máy chủ DNS gốc, sau đó máy chủ gốc sẽ chỉ dẫn đến máy chủ DNS cấp cao nhất (TLD) dựa trên đuôi tên miền (ví dụ: .com, .org). Cuối cùng, máy chủ DNS cục bộ sẽ hỏi máy chủ TLD này và nhận về địa chỉ IP của máy chủ đích. Các địa chỉ IP này thường được lưu vào bộ nhớ cache của máy chủ DNS cục bộ để tăng tốc độ cho các truy vấn sau.
Quá trình từ client đến máy chủ DNS cục bộ là truy vấn đệ quy, còn sự tương tác giữa các máy chủ DNS là truy vấn lặp.
Thiết lập kết nối TCP
Sau khi có địa chỉ IP của máy chủ, trình duyệt sẽ cố gắng thiết lập kết nối TCP với máy chủ. Quá trình này được gọi là "bắt tay ba bước" (three-way handshake). Khi quá trình này hoàn tất, kết nối giữa client và server đã được thiết lập thành công.
Gửi yêu cầu HTTP
Với kết nối TCP đã được thiết lập, trình duyệt sẽ gửi một yêu cầu HTTP đến máy chủ. Yêu cầu này chứa thông tin như phương thức (GET, POST), đường dẫn tài nguyên, các header, và đôi khi là nội dung dữ liệu (payload).
Máy chủ xử lý yêu cầu
Khi máy chủ nhận được yêu cầu HTTP, các máy chủ web như Apache, Nginx, hoặc IIS sẽ tiếp nhận và phân tích nó. Máy chủ web xác định tài nguyên được yêu cầu, xử lý các tham số, có thể tương tác với cơ sở dữ liệu, và cuối cùng tạo ra một phản hồi HTTP.
Phản hồi kết quả
Sau khi xử lý, máy chủ gửi một phản hồi HTTP lại cho trình duyệt. Phản hồi này bao gồm các mã trạng thái HTTP (ví dụ: 200 OK, 404 Not Found, 500 Internal Server Error) để thông báo kết quả của yêu cầu, các HTTP header và nội dung của tài nguyên (ví dụ: mã HTML, CSS, JavaScript).
Đóng kết nối TCP
Để giải phóng tài nguyên, khi không còn dữ liệu cần trao đổi, cả client hoặc server đều có thể khởi tạo việc đóng kết nối TCP. Quá trình này tương tự như "bắt tay ba bước" nhưng được gọi là "bắt tay bốn bước" (four-way handshake).
Phân tích và hiển thị trên trình duyệt
Luồng hiển thị quan trọng (Critical Rendering Path)
Đây là chuỗi các bước mà trình duyệt thực hiện để chuyển đổi HTML, CSS, JavaScript và các tài nguyên khác thành các pixel trên màn hình, giúp người dùng có thể nhìn thấy và tương tác với trang web.
Các bước hiển thị chính
- Phân tích mã HTML để xây dựng Cây DOM (Document Object Model).
- Phân tích mã CSS để xây dựng Cây CSSOM (CSS Object Model).
- Kết hợp Cây DOM và Cây CSSOM để tạo Cây Render (Render Tree).
- Tính toán bố cục (Layout) và vị trí của các phần tử trên trang.
- Vẽ (Paint) các phần tử đã tính toán lên màn hình.
Chi tiết quá trình hiển thị
1. Phân tích HTML để xây dựng Cây DOM
Khi trình duyệt nhận được tài liệu HTML, nó sẽ quét từ trên xuống dưới, phân tích từng byte dữ liệu. Quá trình này bao gồm các bước chuyển đổi từ bytes sang ký tự, sau đó thành các mã token (tokens), rồi thành các đối tượng nút (node objects), và cuối cùng là mô hình đối tượng (object model) của tài liệu HTML, tức Cây DOM. Cây DOM biểu diễn cấu trúc logic của tài liệu, nơi mỗi thẻ HTML trở thành một nút trong cây.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="app.css" rel="stylesheet">
<title>Ví dụ trang web</title>
</head>
<body>
<h1>Chào mừng!</h1>
<p>Đây là một đoạn văn bản <span>đơn giản</span>.</p>
<div><img src="hinh-anh.jpg" alt="Mô tả hình ảnh"></div>
</body>
</html>
2. Phân tích CSS để xây dựng Cây CSSOM
Tương tự như HTML, trình duyệt cũng phân tích các tệp CSS để xây dựng Cây CSSOM. Mỗi tệp CSS được phân tích thành một đối tượng stylesheet, chứa tập hợp các quy tắc CSS. Mỗi quy tắc này bao gồm bộ chọn (selector), khai báo (declaration) và các thuộc tính khác. Cây CSSOM biểu diễn các kiểu dáng đã tính toán cho tất cả các phần tử trong Cây DOM.
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1 {
color: #333;
text-align: center;
}
p {
font-size: 16px;
line-height: 1.5;
}
span {
color: blue;
font-weight: bold;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
}
3. Kết hợp Cây DOM và Cây CSSOM để tạo Cây Render
Trình duyệt sẽ duyệt qua Cây DOM, bắt đầu từ nút gốc, để xác định các phần tử hiển thị. Đối với mỗi nút hiển thị, nó tìm các quy tắc CSSOM phù hợp và áp dụng chúng. Kết quả của quá trình này là Cây Render (hoặc Cây Bố cục), chứa tất cả các phần tử hiển thị trên trang cùng với các kiểu dáng đã tính toán của chúng. Các phần tử không hiển thị (ví dụ: <head>, hoặc các phần tử có display: none;) sẽ không được đưa vào Cây Render.
Mối quan hệ giữa các nút DOM và các đối tượng Render trong Cây Render không phải lúc nào cũng là 1-1. Một nút DOM có thể tương ứng với nhiều đối tượng Render (ví dụ: văn bản bị ngắt dòng tạo ra nhiều hộp dòng), hoặc nhiều nút DOM có thể được nhóm lại thành một đối tượng Render.
4. Tính toán bố cục (Layout) và vị trí
Trong giai đoạn này, trình duyệt sẽ duyệt qua Cây Render để tính toán kích thước và vị trí chính xác của từng đối tượng Render trên màn hình. Kết quả của giai đoạn bố cục là một "mô hình hộp" (box model) cho mỗi phần tử, xác định chính xác tọa độ và kích thước tuyệt đối (tính bằng pixel) của chúng. Đây là lúc tất cả các giá trị tương đối (ví dụ: phần trăm, em) được chuyển đổi thành giá trị tuyệt đối.
5. Vẽ (Paint) các phần tử lên màn hình
Cuối cùng, trình duyệt sẽ phát ra các lệnh vẽ để biến Cây Render thành các pixel trên màn hình. Giai đoạn này bao gồm việc vẽ các thành phần trực quan của từng phần tử (màu nền, màu chữ, viền, hình ảnh, v.v.) theo đúng kích thước và vị trí đã tính toán ở bước bố cục. Thời gian để hoàn thành giai đoạn này tỷ lệ thuận với độ phức tạp của các kiểu dáng CSS. Sau khi quá trình vẽ hoàn tất, người dùng sẽ thấy được trang web hiển thị.
Kiến thức bổ sung
Tải tài nguyên song song
Các trình duyệt hiện đại thường tải tài nguyên một cách song song. Khi trình phân tích HTML bị chặn bởi một thẻ <script>, trình duyệt vẫn tiếp tục quét và xác định các tài nguyên khác phía sau script (như các hình ảnh hoặc tệp CSS) để thực hiện tải trước (preload), giúp tối ưu hóa thời gian tải.
Vẽ lại (Repaint) và Bố cục lại (Reflow/Relayout)
- Vẽ lại (Repaint): Xảy ra khi chỉ có các thuộc tính kiểu dáng không ảnh hưởng đến bố cục của phần tử thay đổi (ví dụ:
background-color,color,visibility). Trình duyệt chỉ cần vẽ lại phần bị ảnh hưởng mà không cần tính toán lại bố cục. - Bố cục lại (Reflow/Relayout): Xảy ra khi các thuộc tính ảnh hưởng đến kích thước hoặc vị trí của một phần tử thay đổi (ví dụ:
width,height,margin,padding,top,left). Điều này khiến trình duyệt phải tính toán lại bố cục cho phần tử đó và có thể cho các phần tử xung quanh, thậm chí là toàn bộ tài liệu. Reflow là một thao tác tốn kém về hiệu năng.
JavaScript chặn phân tích DOM
Do công cụ JavaScript và công cụ giao diện người dùng (GUI engine) thường hoạt động theo cơ chế đơn luồng và độc quyền nhau, khi JavaScript đang thực thi, công cụ GUI sẽ bị tạm dừng, làm gián đoạn quá trình phân tích DOM. Điều này gây ra hiện tượng chặn (blocking). Tuy nhiên, JavaScript chỉ chặn các phần tử DOM theo sau nó, không nhất thiết là toàn bộ DOM. Các phần tử DOM trước script vẫn có thể được phân tích và hiển thị.
CSS chặn phân tích DOM
Thông thường, CSS không chặn quá trình phân tích DOM. Tuy nhiên, nếu một đoạn JavaScript phụ thuộc vào các kiểu dáng CSS (ví dụ: truy vấn element.offsetWidth), và tệp CSS đó chưa được tải hoặc phân tích, trình duyệt sẽ chặn việc thực thi JavaScript cho đến khi CSSOM được xây dựng hoàn chỉnh. Nếu tệp JavaScript được đặt sau tệp CSS và cần các kiểu dáng đó, nó sẽ chặn cả việc phân tích DOM cho đến khi CSS sẵn sàng.
CSS chặn hiển thị DOM
Mặc định, CSS được coi là một tài nguyên chặn hiển thị (render-blocking). Điều này có nghĩa là trình duyệt sẽ không hiển thị bất kỳ nội dung nào cho đến khi Cây CSSOM được xây dựng hoàn chỉnh, vì Cây Render phụ thuộc vào cả DOM và CSSOM. Đây là lý do tại sao chúng ta thường đặt các thẻ <link> CSS trong phần <head> của tài liệu HTML để đảm bảo các kiểu dáng được tải sớm nhất, tránh tình trạng FOUC (Flash Of Unstyled Content) nhưng có thể làm chậm thời gian hiển thị đầu tiên.
Chèn JavaScript và CSS động
- Các tệp CSS và JavaScript bên ngoài được chèn động (ví dụ: sử dụng
document.createElement('script')) thường không chặn phân tích DOM hoặc hiển thị. - Ngược lại, các khối CSS và JavaScript nội tuyến được chèn động vào tài liệu HTML có thể chặn phân tích DOM và hiển thị.
- Các thẻ
<script>được thêm vào thông quainnerHTMLsẽ không được thực thi.
Thay đổi cách JavaScript bị chặn
- Thuộc tính
defertrên thẻ<script>cho phép script được tải song song với việc phân tích HTML và chỉ thực thi sau khi HTML đã được phân tích xong hoàn toàn (trước sự kiệnDOMContentLoaded). Các script códefersẽ được thực thi theo thứ tự xuất hiện trong tài liệu. - Thuộc tính
asynctrên thẻ<script>cũng cho phép script được tải song song. Tuy nhiên, nó sẽ thực thi ngay lập tức sau khi tải xong, không theo thứ tự và có thể làm gián đoạn quá trình phân tích HTML nếu script chưa tải xong. - Cả
defervàasyncchỉ có hiệu lực với các script bên ngoài. Các script được tạo bằngdocument.createElement('script')mặc định hoạt động giống nhưasync.
Các lớp hiển thị (Rendering Layers)
Mô tả
Trình duyệt thường phân chia nội dung thành các lớp để hiển thị. Có hai loại chính: lớp thông thường (normal layers) và lớp kết hợp (composite layers). Toàn bộ luồng tài liệu thông thường có thể được xem là một lớp kết hợp mặc định. Ngay cả các phần tử sử dụng vị trí absolute (hoặc fixed) cũng vẫn nằm trong lớp kết hợp mặc định này.
Tuy nhiên, bạn có thể tạo một lớp kết hợp mới bằng cách kích hoạt tăng tốc phần cứng. Lớp này sẽ được cấp phát tài nguyên riêng và hoạt động độc lập với luồng tài liệu. Bất kỳ thay đổi nào trong lớp kết hợp này sẽ không gây ra reflow hoặc repaint trên lớp kết hợp mặc định, giúp cải thiện hiệu suất.
Về cơ bản, các lớp kết hợp được vẽ độc lập trên GPU, nên chúng không ảnh hưởng lẫn nhau, đó là lý do tại sao tăng tốc phần cứng có thể mang lại hiệu suất rất tốt trong một số trường hợp.
Sự khác biệt giữa position: absolute và tăng tốc phần cứng
Mặc dù position: absolute giúp phần tử thoát khỏi luồng tài liệu thông thường, nó vẫn thuộc về lớp kết hợp mặc định. Do đó, thay đổi thông tin trong một phần tử absolute vẫn có thể ảnh hưởng đến quá trình vẽ của toàn bộ lớp kết hợp mặc định. Nếu lớp này chứa nhiều nội dung hoặc thay đổi quá lớn, việc vẽ lại toàn bộ lớp vẫn tiêu tốn tài nguyên.
Ngược lại, tăng tốc phần cứng tạo ra một lớp kết hợp riêng biệt. Thay đổi trong lớp này chỉ ảnh hưởng đến chính lớp đó và chỉ gây ra quá trình "tổng hợp" (compositing) cuối cùng để hiển thị các lớp lên màn hình, mà không ảnh hưởng đến các lớp khác.
Vai trò của lớp kết hợp
Một phần tử khi được tăng tốc phần cứng sẽ trở thành một lớp kết hợp riêng, độc lập với luồng tài liệu. Điều này giúp tránh việc vẽ lại toàn bộ trang khi phần tử đó thay đổi, nâng cao hiệu suất. Tuy nhiên, việc tạo quá nhiều lớp kết hợp cũng có thể dẫn đến việc tiêu thụ tài nguyên quá mức của GPU và làm chậm trang.
Cách tạo lớp kết hợp (tăng tốc phần cứng)
Một số phương pháp phổ biến để kích hoạt tăng tốc phần cứng và tạo lớp kết hợp:
- Sử dụng các thuộc tính CSS transform 3D như
translate3d(),translateZ(). - Thuộc tính
opacitykhi được thay đổi hoặc trong các animation. - Thuộc tính
will-change(ví dụ:will-change: transform, opacity;) để báo hiệu trước cho trình duyệt về các thay đổi sắp tới, cho phép trình duyệt thực hiện tối ưu hóa trước. Nên sử dụng và loại bỏ thuộc tính này một cách cẩn thận. - Các phần tử như
<video>,<iframe>,<canvas>,<webgl>.
Lưu ý về z-index khi sử dụng tăng tốc phần cứng
Khi một phần tử được tăng tốc phần cứng và có z-index thấp, các phần tử khác nằm sau nó trong DOM nhưng có z-index cao hơn (hoặc cùng cấp và cùng thuộc tính position: relative/absolute) có thể tự động được chuyển thành lớp kết hợp riêng. Đây là khái niệm "tổng hợp ngầm định" (implicit compositing). Việc này có thể gây ra hiệu suất kém nếu không được quản lý tốt, do tạo ra nhiều lớp kết hợp không mong muốn.
Các biện pháp tối ưu hiệu suất
Đối với HTML
- Giảm độ sâu của cấu trúc HTML, lý tưởng là không quá 6 cấp độ.
- Chỉ tải một lượng nhỏ cấu trúc HTML ban đầu cho màn hình đầu tiên (above-the-fold content), các phần còn lại có thể được chèn động.
Đối với CSS
- Sử dụng media queries để giảm lượng CSS cần thiết cho việc xây dựng CSSOM ban đầu.
- Ưu tiên sử dụng ID và Class, tránh lồng ghép quá nhiều selector.
- Giữ cấu trúc kiểu dáng đơn giản.
Đối với JavaScript
- Sử dụng thuộc tính
deferhoặcasynccho các thẻ<script>để tránh chặn quá trình phân tích tài liệu. - Chèn JavaScript động khi có thể để giảm thiểu tác động ban đầu đến hiển thị.
Đối với vị trí tệp
- Đặt các tệp CSS trong phần
<head>để CSSOM có thể được xây dựng sớm. - Đặt các tệp JavaScript ngay trước thẻ
</body>đóng để đảm bảo Cây DOM được xây dựng trước khi JavaScript thực thi. - Tránh đặt tệp JavaScript ngay sau tệp CSS nếu JavaScript đó phụ thuộc vào CSS, điều này có thể gây ra chặn do chờ đợi CSS phân tích.
Đối với tải tài nguyên
- Nén các tài nguyên trang (HTML, CSS, JS, hình ảnh).
- Sử dụng nén gzip cho dữ liệu truyền tải.
- Tận dụng các thuộc tính
relcủa thẻ<link>nhưpreload,preconnect,dns-prefetchđể tải trước và tối ưu hóa kết nối. - Sử dụng HTTP caching hiệu quả để giảm số lượng yêu cầu đến máy chủ.