Tôi có thể chạy javascript trước khi toàn bộ trang được tải không?


96

Tôi muốn chạy một chút javascript trước khi toàn bộ trang được tải. Điều này có khả thi không? Hay mã bắt đầu thực thi trên </html>?



2
Tôi không nghĩ rằng đó là một bản sao - đó là một câu hỏi giao tiếp giữa máy chủ và máy khách.
scunliffe

Câu trả lời:


186

Bạn không chỉ có thể mà còn phải nỗ lực đặc biệt để không làm như vậy nếu bạn không muốn. :-)

Khi trình duyệt gặp một scriptthẻ cổ điển khi phân tích cú pháp HTML, nó sẽ ngừng phân tích cú pháp và chuyển giao cho trình thông dịch JavaScript chạy tập lệnh. Trình phân tích cú pháp không tiếp tục cho đến khi quá trình thực thi tập lệnh hoàn tất (vì tập lệnh có thể thực hiện document.writecác lệnh gọi đánh dấu đầu ra mà trình phân tích cú pháp sẽ xử lý).

Đó là hành vi mặc định, nhưng bạn có một số tùy chọn để trì hoãn việc thực thi tập lệnh:

  1. Sử dụng mô-đun JavaScript. Tập type="module"lệnh được hoãn lại cho đến khi HTML được phân tích cú pháp hoàn toàn và DOM ban đầu được tạo. Đây không phải là lý do chính để sử dụng mô-đun, nhưng đó là một trong những lý do:

    <script type="module" src="./my-code.js"></script>
    <!-- Or -->
    <script type="module">
    // Your code here
    </script>

    Mã sẽ được tìm nạp (nếu nó riêng biệt) và được phân tích cú pháp song song với quá trình phân tích cú pháp HTML, nhưng sẽ không được chạy cho đến khi quá trình phân tích cú pháp HTML hoàn tất. (Nếu mã mô-đun của bạn nằm trong dòng chứ không phải trong tệp của chính nó, nó cũng được hoãn lại cho đến khi phân tích cú pháp HTML hoàn tất.)

    Điều này không có sẵn khi tôi viết câu trả lời này lần đầu tiên vào năm 2010, nhưng ở đây vào năm 2020, tất cả các trình duyệt hiện đại chính đều hỗ trợ mô-đun nguyên bản và nếu bạn cần hỗ trợ các trình duyệt cũ hơn, bạn có thể sử dụng các gói như Webpack và Rollup.js.

  2. Sử dụng deferthuộc tính trên thẻ script cổ điển:

    <script defer src="./my-code.js"></script>

    Cũng như với mô-đun, mã trong my-code.jssẽ được tìm nạp và phân tích cú pháp song song với phân tích cú pháp HTML, nhưng sẽ không được chạy cho đến khi phân tích cú pháp HTML hoàn tất. Tuy nhiên , deferkhông hoạt động với nội dung tập lệnh nội tuyến, chỉ với các tệp bên ngoài được tham chiếu qua src.

  3. Tôi không nghĩ đó là những gì bạn muốn, nhưng bạn có thể sử dụng asyncthuộc tính để yêu cầu trình duyệt tìm nạp mã JavaScript song song với phân tích cú pháp HTML, nhưng sau đó chạy nó ngay khi có thể, ngay cả khi phân tích cú pháp HTML chưa hoàn tất . Bạn có thể đặt nó trên một type="module"thẻ hoặc sử dụng nó thay vì defertrên một scriptthẻ cổ điển .

  4. Đặt scriptthẻ ở cuối tài liệu, ngay trước </body>thẻ đóng :

    <!doctype html>
    <html>
    <!-- ... -->
    <body>
    <!-- The document's HTML goes here -->
    <script type="module" src="./my-code.js"></script><!-- Or inline script -->
    </body>
    </html>

    Bằng cách đó, mặc dù mã được chạy ngay khi gặp phải, tất cả các phần tử được HTML định nghĩa ở trên nó đều tồn tại và sẵn sàng được sử dụng.

    Điều này đã từng gây ra sự chậm trễ bổ sung trên một số trình duyệt vì chúng sẽ không bắt đầu tìm nạp mã cho đến khi scriptgặp thẻ, nhưng các trình duyệt hiện đại sẽ quét trước và bắt đầu tìm nạp trước. Tuy nhiên, đây rất nhiều là sự lựa chọn thứ ba tại thời điểm này, cả hai mô-đun và deferlà những lựa chọn tốt hơn.

Spec có một sơ đồ hữu ích cho thấy một nguyên scripttag, defer, async, type="module", và type="module" asyncvà thời gian khi các mã JavaScript được nạp và chạy:

nhập mô tả hình ảnh ở đây

Đây là ví dụ về hành vi mặc định, một scriptthẻ thô :

.found {
    color: green;
}
<p>Paragraph 1</p>
<script>
    if (typeof NodeList !== "undefined" && !NodeList.prototype.forEach) {
        NodeList.prototype.forEach = Array.prototype.forEach;
    }
    document.querySelectorAll("p").forEach(p => {
        p.classList.add("found");
    });
</script>
<p>Paragraph 2</p>

(Xem câu trả lời của tôi ở đây để biết chi tiết xung quanh NodeListmã đó .)

Khi bạn chạy điều đó, bạn thấy "Đoạn 1" có màu xanh lục nhưng "Đoạn 2" là màu đen, bởi vì tập lệnh chạy đồng bộ với phân tích cú pháp HTML, và vì vậy nó chỉ tìm thấy đoạn đầu tiên chứ không phải đoạn thứ hai.

Ngược lại, đây là một type="module"tập lệnh:

.found {
    color: green;
}
<p>Paragraph 1</p>
<script type="module">
    document.querySelectorAll("p").forEach(p => {
        p.classList.add("found");
    });
</script>
<p>Paragraph 2</p>

Lưu ý rằng cả hai đều xanh bây giờ; mã đã không chạy cho đến khi phân tích cú pháp HTML hoàn tất. Điều đó cũng đúng với một defer scriptcó nội dung bên ngoài (nhưng không đúng với nội dung bên trong).

(Không có nhu cầu sử dụng NodeListséc đó vì bất kỳ trình duyệt hiện đại hỗ trợ module đã có forEachtrên NodeList.)

Trong thế giới hiện đại này, không có giá trị thực sự nào đối với DOMContentLoadedsự kiện của tính năng "sẵn sàng" mà PrototypeJS, jQuery, ExtJS, Dojo và hầu hết các tính năng khác đã cung cấp trong ngày (và vẫn đang cung cấp); chỉ sử dụng các mô-đun hoặc defer. Ngay cả trước đây, không có nhiều lý do để sử dụng chúng (và chúng thường được sử dụng không đúng cách, giữ bản trình bày trang trong khi toàn bộ thư viện jQuery được tải vì scriptnó nằm trong headthay vì sau tài liệu), một số nhà phát triển tại Google đã gắn cờ sớm. Đây cũng là một phần lý do khiến YUI đề xuất đặt các tập lệnh vào cuối body, một lần nữa trong ngày.


1
@FeRD - Cảm ơn bạn đã chỉnh sửa. Những người đánh giá đã từ chối nó, nhưng bạn đã khá đúng. :-)
TJ Crowder

Hoặc đặt tất cả trong một chức năng và đưa nó vào <body onload="doIt()>".
Justin Liu,

@JustinLiu - Sự loadkiện xảy ra rất muộn trong chu kỳ tải trang, hầu như không bao giờ là phương pháp hay nhất là đợi để chạy JavaScript của bạn cho đến lúc đó. Nhưng có, nó là một lựa chọn. Tôi đã đại tu câu trả lời cho năm 2020, btw.
TJ Crowder

Hoặc bạn có thể sử dụngdocument.addEventListener('DOMContentLoaded', function(){ //doyour stuff })
Justin Liu

@JustinLiu - Đúng vậy, hãy xem đoạn cuối. :-) Tôi chưa bao giờ thấy cần phải làm như vậy, nhưng bạn có thể.
TJ Crowder

6

Bạn có thể chạy mã javascript bất cứ lúc nào. AFAIK nó được thực thi tại thời điểm trình duyệt đạt đến thẻ <script> ở vị trí của nó. Nhưng bạn không thể truy cập các phần tử chưa được tải.

Vì vậy, nếu bạn cần quyền truy cập vào các phần tử, bạn nên đợi cho đến khi DOM được tải (điều này không có nghĩa là toàn bộ trang được tải, bao gồm cả hình ảnh và nội dung. Đó chỉ là cấu trúc của tài liệu, được tải sớm hơn nhiều, vì vậy bạn thường thắng 't nhận thấy sự chậm trễ), sử dụng DOMContentLoadedsự kiện hoặc các chức năng như $.readytrong jQuery.


2
Sự kiện DOMContentLoaded sẽ kích hoạt ngay sau khi cấu trúc phân cấp DOM đã được xây dựng hoàn chỉnh, sự kiện tải sẽ thực hiện khi tất cả hình ảnh và khung phụ đã tải xong.
BeauCielBleu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.