Nếu bạn dành cả ngày để viết mã, bạn sẽ dành hàng giờ bên trong hai môi trường: cửa sổ trình duyệt nơi công việc của bạn thực sự chạy, và kho lưu trữ Git nơi ghi nhớ mọi quyết định bạn đã đưa ra để đạt được kết quả đó. Một bên hướng ra công chúng và khó đoán, bên kia thì riêng tư và khắt khe. Hiểu rõ cả hai là điều bắt buộc. Việc thành thạo bộ máy nội bộ của trình duyệt và logic staging của Git sẽ phân biệt những lập trình viên chỉ làm việc theo cảm tính với những người biết chính xác tại sao thứ gì đó bị lỗi và nó đã thay đổi khi nào.

Cấu trúc của URL

Mọi chuyến truy cập vào một trang web đều bắt đầu bằng một chuỗi các ký tự trông có vẻ đơn giản nhưng lại mang theo những chỉ dẫn chính xác. Một URL như https://shop.example.com:443/products/id/42?sort=price#reviews thực chất là một tập hợp các chỉ dẫn riêng biệt.

Protocol (giao thức) nằm ở phía trước và thiết lập các quy tắc cho cuộc hội thoại. Khi bạn thấy https://, trình duyệt biết rằng nó cần mã hóa kết nối trước khi gửi bất cứ thứ gì. Domain (shop.example.com) là tên dễ đọc dành cho địa chỉ mạng thực tế của máy chủ. Nó được phân giải thông qua DNS để máy tính của bạn biết nơi cần kết nối. Port (:443) là cửa ngõ cụ thể trên máy chủ đó. Nó thường ẩn đi vì các trình duyệt mặc định cổng 443 cho HTTPS và 80 cho HTTP, nhưng nó luôn tồn tại trong cơ chế hoạt động. Path (/products/id/42) cho máy chủ biết tài nguyên nào bạn muốn, được tổ chức giống như các thư mục. Query string (?sort=price) chuyển giao dữ liệu động dưới dạng các cặp key-value, hoàn hảo cho các bộ lọc, thuật ngữ tìm kiếm hoặc phân trang. Cuối cùng, fragment (#reviews) trỏ đến một ID phần tử cụ thể trên trang. Nó không bao giờ gửi đến máy chủ; trình duyệt sẽ xử lý nó hoàn toàn ở phía client sau khi trang đã tải xong.

Hãy luôn để fragment ở cuối. Nếu bạn di chuyển nó lên trước query string, bạn sẽ làm hỏng liên kết vì mọi thứ sau dấu thăng (#) đều được coi là ngữ cảnh phía client, không phải chỉ dẫn cho máy chủ.

DOM: Hệ thần kinh sống của trang web

HTML truyền qua mạng chỉ là văn bản thuần túy. Trình duyệt đọc văn bản đó và xây dựng Document Object Model, một bản đồ dạng cây sống gồm các đối tượng được gọi là các nodes. Các thẻ element trở thành element nodes. Văn bản giữa các thẻ trở thành text nodes. Ngay cả các thuộc tính (attributes) và chú thích (comments) cũng có các loại node riêng. Cấu trúc cây này không phải là một sơ đồ tĩnh. Nó là một cấu trúc dữ liệu sống mà JavaScript có thể đọc và ghi đè ngay lập tức.

Khi script của bạn chạy document.getElementById hoặc thay đổi một className, bạn đang can thiệp vào cây này và làm thay đổi nó. Trình duyệt nhận thấy điều đó và vẽ lại (repaint) màn hình mà không cần yêu cầu máy chủ gửi một trang mới. Sức mạnh đó chính là thứ giúp các ứng dụng web hiện đại trở nên khả thi, nhưng nó cũng đi kèm với một cái giá. Mỗi khi bạn chạm vào DOM, trình duyệt có thể phải tính toán lại bố cục (layout) và kiểu dáng (styles). Nếu bạn thực hiện việc đó trong một vòng lặp chặt chẽ với hàng trăm mục, tốc độ khung hình (frame rate) của bạn sẽ giảm mạnh. Nếu bạn cần chèn một danh sách dài, hãy xây dựng một DocumentFragment trong bộ nhớ trước, sau đó mới append nó một lần duy nhất. Hãy gom nhóm các thao tác đọc và ghi (batch your reads and writes). DOM rất linh hoạt, nhưng nó không hề miễn phí.

Browser Storage: Ba công cụ, ba nhiệm vụ

Các trình duyệt hiện đại cho phép bạn lưu trữ dữ liệu trực tiếp trên máy của người dùng, và việc chọn đúng cơ chế là rất quan trọng vì mỗi cơ chế được xây dựng cho một vòng đời và dung lượng khác nhau.

LocalStorage là đơn giản nhất. Nó lưu trữ một lượng nhỏ dữ liệu dạng chuỗi (string) một cách vĩnh viễn cho đến khi mã của bạn hoặc người dùng xóa nó. Một trường hợp sử dụng điển hình là tùy chọn chế độ tối (dark-mode). Khi ai đó bật công tắc, hãy ghi "theme": "dark" vào LocalStorage. Trong lần truy cập tiếp theo, hãy đọc lại nó và áp dụng class trước khi lần vẽ đầu tiên (first paint) diễn ra. Nó hoạt động đồng bộ (synchronous) và được giới hạn trong phạm vi origin, điều này giúp nó dễ sử dụng nhưng cũng có nghĩa là bạn không bao giờ nên lưu các token nhạy cảm vào đó. Bất kỳ script nào chạy trên trang của bạn đều có thể đọc được nó.

SessionStorage sử dụng cùng một API key-value, nhưng vòng đời của nó gắn liền với tab trình duyệt. Nó tồn tại qua các lần tải lại trang, điều này khiến nó trở nên hoàn hảo cho việc lưu tiến trình biểu mẫu tạm thời. Hãy tưởng tượng một người dùng đang điền một bản khảo sát dài, vô tình nhấn tải lại trang, và vẫn thấy các câu trả lời của họ vì bạn đã cất chúng vào SessionStorage. Khi họ đóng tab, dữ liệu sẽ tự động được dọn dẹp.

Cache API hoạt động ở một quy mô khác. Nó lưu trữ các cặp request và response, thường được sử dụng bởi service workers để lưu trữ các tài nguyên tĩnh lớn như hình ảnh, font chữ và các gói script. Thay vì phải tải lại cùng một hình ảnh hero hay gói React qua mạng trong mỗi lần truy cập, ứng dụng của bạn có thể phục vụ chúng trực tiếp từ bộ nhớ đệm đĩa (disk cache). Đó là cách các trang web có khả năng hoạt động ngoại tuyến (offline-capable) tải tức thì trong các lần truy cập lặp lại. Nó không phải là một kho lưu trữ key-value thông thường như hai loại kia; nó được xây dựng chuyên biệt cho các HTTP response.

Một quy tắc bất di bất dịch: đừng bao giờ lưu trữ các token xác thực hoặc thông tin định danh cá nhân trong LocalStorage. Các cuộc tấn công XSS có thể quét sạch chúng chỉ trong vài mili giây. Hãy sử dụng cookie HttpOnly, Secure, SameSite cho bất kỳ dữ liệu nhạy cảm nào, và kiểm tra chúng trong tab Application để xác nhận rằng các cờ (flags) thực sự đã được thiết lập.

Browser DevTools: Ngừng đoán mò, hãy bắt đầu đọc

Bảng DevTools không chỉ dùng để sửa các lỗi console màu đỏ. Nó là phòng thí nghiệm chẩn đoán cho mọi thứ đang diễn ra bên trong trình duyệt.

Trong bảng Elements, bạn có thể di chuột qua cây DOM và quan sát các node được làm nổi bật trên trang theo thời gian thực. Bạn có thể chỉnh sửa trực tiếp các giá trị CSS trong ngăn Styles để kiểm tra margin hoặc màu sắc trước khi chạm vào mã nguồn của mình. Console là cuốn sổ nháp của bạn. Hãy log các object, kiểm tra regex, hoặc gọi các hàm trực tiếp dựa trên trạng thái hiện tại của trang. Nếu một biến hoạt động không đúng ý, hãy gõ tên của nó và kiểm tra trực tiếp.

Tab Network tiết lộ sự thật về hiệu suất. Trang web tải chậm có thể không phải do JavaScript của bạn. Có thể là do một font chữ từ bên thứ ba mất bốn giây để phản hồi, hoặc một API endpoint trả về một payload JSON nặng hai megabyte mà bạn chưa bao giờ nén. Bạn có thể theo dõi toàn bộ vòng đời của mọi request, lọc theo Fetch/XHR để theo dõi các cuộc gọi API của chính mình, và kiểm tra các header để xem liệu các chỉ thị bộ nhớ đệm (caching directives) có đang được tuân thủ hay không. Trong khi đó, tab Application cho phép bạn kiểm tra kho lưu trữ của mình. Hãy xem qua các cặp key-value trong LocalStorage, kiểm tra từng cookie và các cờ của chúng, và xác nhận rằng service worker của bạn thực sự đã được đăng ký và đang lưu bộ nhớ đệm những gì bạn mong đợi.

Git Workflow: Ba ngăn chứa

Git không phải là phần mềm sao lưu. Nó là một công cụ để quản lý lịch sử. Suy nghĩ theo cách đó sẽ thay đổi cách bạn sử dụng nó. Git quản lý dự án của bạn thông qua ba khu vực riêng biệt.

working tree là chiếc bàn làm việc bừa bộn của bạn. Bạn chỉnh sửa tệp, xóa thư mục và thử nghiệm tại đây. Chưa có gì là an toàn cả. staging area, hay index, là nơi bạn lựa chọn một cách có chọn lọc những gì sẽ đưa vào snapshot tiếp theo. Chạy git add trên một tệp sẽ chuyển nó từ working tree vào staging. Điều này mang lại cho bạn sự chính xác. Bạn có thể sửa mười tệp, chỉ đưa ba tệp vào staging, và commit một snapshot sạch sẽ, logic, thực sự mô tả một thay đổi duy nhất. local repository sẽ nhận snapshot khi bạn chạy git commit. Tại thời điểm đó, Git ghi lại toàn bộ trạng thái của các tệp đã được staging cùng với thông điệp của bạn, tạo ra một điểm kiểm tra (checkpoint) vĩnh viễn mà bạn có thể quay lại sau này.

Trước khi đưa bất cứ thứ gì vào staging, hãy chạy git status. Nó sẽ hiển thị cho bạn các tệp chưa được theo dõi (untracked files) và các tệp đã chỉnh sửa mà bạn có thể đã quên. Các tệp build tạm thời, tệp log, hoặc tệp môi trường có thể bị lọt vào các commit nếu bạn bỏ qua bước kiểm tra này. Một tệp .gitignore chuẩn chỉnh sẽ giúp ích, nhưng git status mới là bước kiểm tra cuối cùng trước khi "cất cánh".

Staging cũng cho phép bạn sửa lỗi trước khi chúng trở thành lịch sử. Hãy đưa một tệp ra khỏi staging bằng git restore --staged nếu bạn đã thêm nó quá sớm. Hãy viết lại commit message nếu bạn viết quá mơ hồ. Staging area tồn tại chính xác để các commit của bạn kể một câu chuyện mạch lạc, chứ không chỉ là một bản đổ dữ liệu thô của mọi phím bấm bạn đã nhấn kể từ giờ nghỉ trưa.

Kết nối mọi thứ lại với nhau

Hai lĩnh vực này, trình duyệt và Git, định hình hầu như mọi giờ làm việc trong quy trình của bạn. Trong trình duyệt, bạn cần hiểu cách các request được giải quyết, cách DOM phản ứng với các script của bạn và dữ liệu nằm ở đâu trên client. Việc lạm dụng LocalStorage để lưu trữ các thông tin bí mật hoặc làm quá tải DOM bằng các cập nhật không được gom nhóm (unbatched updates) sẽ tạo ra các ứng dụng kém ổn định và chậm chạp. Trong terminal, việc coi Git như một nút "lưu" (save button) sẽ tạo ra một lịch sử mà không ai, kể cả chính bạn trong tương lai, có thể đọc được. Hãy sử dụng staging area một cách có chủ đích. Kiểm tra trạng thái của bạn. Viết các commit giải thích lý do tại sao, chứ không chỉ là cái gì.

Thói quen gắn kết cả hai thế giới này chính là sự kiểm tra. Hãy điều tra các URL trước khi đổ lỗi cho API. Hãy profile DOM trước khi thêm một framework. Hãy đọc tab Network trước khi mua một máy chủ lớn hơn. Hãy xem lại git status trước khi chốt một sai lầm. Các công cụ đã mở sẵn trên màn hình của bạn rồi. Học cách đọc chúng một cách trung thực chính là công việc của bạn.