tải và thực hiện thứ tự các tập lệnh


265

Có rất nhiều cách khác nhau để đưa JavaScript vào trang html. Tôi biết về các tùy chọn sau:

  • mã nội tuyến hoặc được tải từ URI bên ngoài
  • được bao gồm trong thẻ <head> hoặc <body> [ 1 , 2 ]
  • không có deferhoặc asyncthuộc tính (chỉ các tập lệnh bên ngoài)
  • được bao gồm trong nguồn tĩnh hoặc được thêm động bởi các tập lệnh khác (ở các trạng thái phân tích khác nhau, với các phương thức khác nhau)

Không tính các trình duyệt từ đĩa cứng, javascript: URI và onEvent-attribution [ 3 ], đã có 16 lựa chọn thay thế để thực thi JS và tôi chắc chắn mình đã quên một cái gì đó.

Tôi không quá quan tâm đến việc tải nhanh (song song), tôi tò mò hơn về thứ tự thực hiện (có thể phụ thuộc vào thứ tự tải và thứ tự tài liệu ). Có một tài liệu tham khảo (trình duyệt chéo) tốt bao gồm tất cả các trường hợp? Ví dụ: http://www.websiteoptimization.com/speed/tweak/defer/ chỉ giao dịch với 6 người trong số họ và kiểm tra hầu hết các trình duyệt cũ.

Vì tôi sợ là không có, đây là câu hỏi cụ thể của tôi: Tôi đã có một số tập lệnh chính (bên ngoài) để khởi tạo và tải tập lệnh. Sau đó, tôi đã có hai tập lệnh nội tuyến tĩnh ở cuối cơ thể. Cái đầu tiên cho phép trình tải tập lệnh tự động nối thêm một phần tử script khác (tham chiếu js bên ngoài) vào phần thân. Thứ hai của các tập lệnh nội tuyến tĩnh muốn sử dụng js từ tập lệnh bên ngoài được thêm vào. Nó có thể dựa vào cái khác đã được thực thi (và tại sao :-)?


Bạn đã xem Tải tập lệnh mà không bị chặn bởi Steve Souder chưa? Bây giờ nó hơi cũ, nhưng vẫn chứa một số hiểu biết có giá trị về hành vi trình duyệt được cung cấp một kỹ thuật tải tập lệnh cụ thể.
Josh Habdas

Câu trả lời:


331

Nếu bạn không tự động tải tập lệnh hoặc đánh dấu chúng là deferhoặc async, thì tập lệnh được tải theo thứ tự gặp phải trong trang. Không quan trọng đó là tập lệnh bên ngoài hay tập lệnh nội tuyến - chúng được thực thi theo thứ tự chúng gặp trong trang. Các tập lệnh nội tuyến xuất hiện sau các tập lệnh bên ngoài được giữ cho đến khi tất cả các tập lệnh bên ngoài xuất hiện trước khi chúng được tải và chạy.

Các tập lệnh async (bất kể chúng được chỉ định là async như thế nào) tải và chạy theo thứ tự không thể đoán trước. Trình duyệt tải chúng song song và có thể tự do chạy chúng theo bất kỳ thứ tự nào nó muốn.

Không có thứ tự dự đoán trong số nhiều thứ không đồng bộ. Nếu ai đó cần một thứ tự dự đoán, thì nó sẽ phải được mã hóa bằng cách đăng ký thông báo tải từ các tập lệnh async và sắp xếp các lệnh gọi javascript theo cách thủ công khi những thứ thích hợp được tải.

Khi thẻ script được chèn động, cách thức thực hiện lệnh sẽ phụ thuộc vào trình duyệt. Bạn có thể thấy Firefox hoạt động như thế nào trong bài viết tham khảo này . Tóm lại, các phiên bản mới hơn của Firefox mặc định thẻ script được thêm động vào async trừ khi thẻ script được đặt khác.

Thẻ script có asyncthể được chạy ngay khi được tải. Trong thực tế, trình duyệt có thể tạm dừng trình phân tích cú pháp từ bất cứ điều gì khác mà nó đang làm và chạy tập lệnh đó. Vì vậy, nó thực sự có thể chạy bất cứ lúc nào. Nếu tập lệnh được lưu trữ, nó có thể chạy gần như ngay lập tức. Nếu tập lệnh mất một lúc để tải, nó có thể chạy sau khi trình phân tích cú pháp hoàn tất. Một điều cần nhớ asynclà nó có thể chạy bất cứ lúc nào và thời gian đó là không thể dự đoán được.

Thẻ script deferchờ đợi cho đến khi toàn bộ trình phân tích cú pháp được thực hiện và sau đó chạy tất cả các tập lệnh được đánh dấu theo deferthứ tự chúng gặp phải. Điều này cho phép bạn đánh dấu một số tập lệnh phụ thuộc lẫn nhau như defer. Tất cả họ sẽ bị hoãn lại cho đến sau khi trình phân tích cú pháp tài liệu được thực hiện, nhưng họ sẽ thực hiện theo thứ tự họ gặp phải để duy trì sự phụ thuộc của họ. Tôi nghĩ defergiống như các tập lệnh được thả vào hàng đợi sẽ được xử lý sau khi trình phân tích cú pháp hoàn tất. Về mặt kỹ thuật, trình duyệt có thể tải xuống các tập lệnh ở chế độ nền bất cứ lúc nào, nhưng chúng sẽ không thực thi hoặc chặn trình phân tích cú pháp cho đến khi trình phân tích cú pháp hoàn tất phân tích trang và phân tích cú pháp và chạy bất kỳ tập lệnh nội tuyến nào không được đánh dấu deferhoặc async.

Đây là một trích dẫn từ bài viết đó:

tập lệnh được chèn tập lệnh thực thi không đồng bộ trong IE và WebKit, nhưng đồng bộ trong Opera và Firefox trước 4.0.

Phần có liên quan của thông số HTML5 (dành cho các trình duyệt tuân thủ mới hơn) có tại đây . Có rất nhiều điều được viết trong đó về hành vi không đồng bộ. Rõ ràng, thông số kỹ thuật này không áp dụng cho các trình duyệt cũ hơn (hoặc các trình duyệt không phù hợp) có hành vi mà bạn có thể phải kiểm tra để xác định.

Một trích dẫn từ thông số HTML5:

Sau đó, các tùy chọn đầu tiên sau đây mô tả tình huống phải được tuân theo:

Nếu phần tử có thuộc tính src và phần tử có thuộc tính defer và phần tử đã được gắn cờ là "trình phân tích cú pháp được chèn" và phần tử không có thuộc tính async Phần tử phải được thêm vào cuối danh sách các tập lệnh sẽ thực thi khi tài liệu đã phân tích xong liên kết với Tài liệu của trình phân tích cú pháp đã tạo ra phần tử.

Tác vụ mà nguồn tác vụ mạng đặt trên hàng đợi tác vụ khi thuật toán tìm nạp đã hoàn thành phải đặt cờ "sẵn sàng để được phân tích cú pháp" của phần tử. Trình phân tích cú pháp sẽ xử lý việc thực thi tập lệnh.

Nếu phần tử có thuộc tính src và phần tử đã được gắn cờ là "trình phân tích cú pháp được chèn" và phần tử không có thuộc tính async Phần tử là tập lệnh chặn phân tích cú pháp đang chờ xử lý của Tài liệu của trình phân tích cú pháp đã tạo phần tử. (Mỗi lần chỉ có thể có một tập lệnh như vậy cho mỗi Tài liệu.)

Tác vụ mà nguồn tác vụ mạng đặt trên hàng đợi tác vụ khi thuật toán tìm nạp đã hoàn thành phải đặt cờ "sẵn sàng để được phân tích cú pháp" của phần tử. Trình phân tích cú pháp sẽ xử lý việc thực thi tập lệnh.

Nếu phần tử không có thuộc tính src và phần tử đã được gắn cờ là "trình phân tích cú pháp được chèn" và Tài liệu của trình phân tích cú pháp HTML hoặc trình phân tích cú pháp XML đã tạo phần tử tập lệnh có một biểu định kiểu đang chặn các tập lệnh Phần tử là tập lệnh chặn phân tích cú pháp đang chờ xử lý của Tài liệu của trình phân tích cú pháp đã tạo phần tử. (Mỗi lần chỉ có thể có một tập lệnh như vậy cho mỗi Tài liệu.)

Đặt cờ "sẵn sàng để được phân tích cú pháp" của phần tử. Trình phân tích cú pháp sẽ xử lý việc thực thi tập lệnh.

Nếu phần tử có thuộc tính src, không có thuộc tính async và không có bộ cờ "force-async" Phần tử phải được thêm vào cuối danh sách các tập lệnh sẽ thực hiện theo thứ tự càng sớm càng tốt với Tài liệu của phần tử tập lệnh tại thời điểm chuẩn bị thuật toán tập lệnh.

Tác vụ mà nguồn tác vụ mạng đặt trên hàng đợi tác vụ khi thuật toán tìm nạp đã hoàn thành phải chạy các bước sau:

Nếu phần tử bây giờ không phải là phần tử đầu tiên trong danh sách các tập lệnh sẽ thực thi theo thứ tự càng sớm càng tốt, thì đánh dấu phần tử là đã sẵn sàng nhưng hủy bỏ các bước này mà không thực thi tập lệnh.

Thực thi: Thực thi khối tập lệnh tương ứng với phần tử tập lệnh đầu tiên trong danh sách tập lệnh này sẽ thực hiện theo thứ tự càng sớm càng tốt.

Xóa phần tử đầu tiên khỏi danh sách các tập lệnh này sẽ thực thi theo thứ tự càng sớm càng tốt.

Nếu danh sách các tập lệnh này sẽ thực thi theo thứ tự càng sớm càng tốt vẫn không trống và mục nhập đầu tiên đã được đánh dấu là đã sẵn sàng, sau đó quay lại bước thực hiện có nhãn.

Nếu phần tử có thuộc tính src Phần tử phải được thêm vào tập lệnh sẽ thực thi càng sớm càng tốt đối với Tài liệu của phần tử tập lệnh tại thời điểm chuẩn bị thuật toán tập lệnh.

Tác vụ mà nguồn tác vụ mạng đặt trên hàng đợi tác vụ khi thuật toán tìm nạp đã hoàn thành phải thực thi khối tập lệnh và sau đó xóa phần tử khỏi bộ tập lệnh sẽ thực thi càng sớm càng tốt.

Nếu không Các user agent phải thực hiện ngay lập tức khối kịch bản, ngay cả khi kịch bản khác đã được thực hiện.


Còn các kịch bản mô-đun Javascript thì type="module"sao?

Javascript hiện có hỗ trợ tải mô-đun với cú pháp như thế này:

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

Hoặc, với srcthuộc tính:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

Tất cả các tập lệnh với type="module"được tự động đưa ra deferthuộc tính. Điều này tải chúng song song (nếu không phải là nội tuyến) với việc tải trang khác và sau đó chạy chúng theo thứ tự, nhưng sau khi trình phân tích cú pháp hoàn tất.

Các tập lệnh mô-đun cũng có thể được cung cấp asyncthuộc tính sẽ chạy các tập lệnh mô-đun nội tuyến càng sớm càng tốt, không phải đợi cho đến khi trình phân tích cú pháp hoàn thành và không chờ chạy asynctập lệnh theo bất kỳ thứ tự cụ thể nào so với các tập lệnh khác.

Có một biểu đồ dòng thời gian khá hữu ích cho thấy tìm nạp và thực thi các tổ hợp tập lệnh khác nhau, bao gồm các tập lệnh mô-đun ở đây trong bài viết này: Đang tải Mô-đun Javascript .


Cảm ơn câu trả lời, nhưng vấn đề là tập lệnh được tự động thêm vào trang, điều đó có nghĩa là nó được coi là không đồng bộ . Hay nó chỉ hoạt động trong <head>? Và kinh nghiệm của tôi cũng là họ được thực hiện theo thứ tự tài liệu?
Bergi

@Bergi - Nếu được thêm động, thì đó là async và thứ tự thực thi là không xác định trừ khi bạn viết mã để kiểm soát nó.
jfriend00

Chỉ là, Kolink nói ngược lại ...
Bergi

@Bergi - OK, tôi đã sửa đổi câu trả lời của mình để nói rằng các tập lệnh async tải theo thứ tự không xác định. Chúng có thể được tải theo bất kỳ thứ tự nào. Nếu tôi là bạn, tôi sẽ không tin vào quan sát của Kolink theo cách nó luôn như vậy. Tôi biết không có tiêu chuẩn nào nói rằng tập lệnh được thêm động phải được chạy ngay lập tức và phải chặn các tập lệnh khác chạy cho đến khi được tải. Tôi hy vọng rằng nó phụ thuộc vào trình duyệt và có lẽ phụ thuộc vào các yếu tố môi trường (liệu tập lệnh có được lưu trong bộ nhớ cache hay không, v.v.).
jfriend00

1
@RuudLender - Đó là tùy thuộc vào việc thực hiện trình duyệt. Gặp thẻ script sớm hơn trong tài liệu, nhưng được đánh dấu bằng defercho trình phân tích cú pháp cơ hội để bắt đầu tải xuống sớm hơn trong khi vẫn hoãn thực thi. Lưu ý rằng nếu bạn có nhiều tập lệnh từ cùng một máy chủ, thì việc bắt đầu tải xuống sớm hơn có thể thực sự làm chậm quá trình tải xuống của những người khác từ cùng một máy chủ (vì chúng cạnh tranh về băng thông) mà trang của bạn đang chờ (không phải defervậy) đây có thể là con dao hai lưỡi.
jfriend00

13

Trình duyệt sẽ thực thi các tập lệnh theo thứ tự tìm thấy chúng. Nếu bạn gọi một tập lệnh bên ngoài, nó sẽ chặn trang cho đến khi tập lệnh được tải và thực thi.

Để kiểm tra thực tế này:

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

Các tập lệnh được thêm động được thực thi ngay khi chúng được thêm vào tài liệu.

Để kiểm tra thực tế này:

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

Thứ tự thông báo là "được thêm" -> "xin chào!" -> "cuối cùng"

Nếu trong tập lệnh bạn cố truy cập một phần tử chưa đạt được (ví dụ <script>do something with #blah</script><div id="blah"></div>:) thì bạn sẽ gặp lỗi.

Nhìn chung, có, bạn có thể bao gồm các tập lệnh bên ngoài và sau đó truy cập các chức năng và biến của chúng, nhưng chỉ khi bạn thoát khỏi <script>thẻ hiện tại và bắt đầu một tập lệnh mới.


Tôi có thể xác nhận hành vi đó. Nhưng có những gợi ý trên các trang phản hồi của chúng tôi, rằng nó chỉ có thể hoạt động khi test.php được lưu trữ. Bạn có biết bất kỳ liên kết spec / tham khảo về điều này?
Bergi

4
link.js không chặn. Sử dụng một tập lệnh tương tự như php của bạn để mô phỏng thời gian tải xuống dài.
1983

14
Câu trả lời này không chính xác. Không phải lúc nào "các tập lệnh được thêm động được thực thi ngay khi chúng được thêm vào tài liệu". Đôi khi điều này là đúng (ví dụ: đối với các phiên bản Firefox cũ), nhưng thường thì không. Thứ tự thực hiện, như được đề cập trong câu trả lời của jfriend00, không xác định.
Fabio Beltramini

1
Nó không có nghĩa là các tập lệnh được thực hiện theo thứ tự chúng xuất hiện trên trang bất kể chúng có nội tuyến hay không. Tại sao sau đó đoạn mã quản lý thẻ Google và nhiều đoạn mã khác mà tôi đã thấy, có mã để chèn một tập lệnh mới trên tất cả các thẻ tập lệnh khác trong trang? Sẽ không có nghĩa gì khi làm điều này, nếu các đoạn script trên đã được tải chắc chắn ?? hoặc tôi đang thiếu một cái gì đó.
dùng3094826


2

Sau khi thử nghiệm nhiều tùy chọn, tôi thấy rằng giải pháp đơn giản sau đây đang tải các tập lệnh được tải động theo thứ tự chúng được thêm vào trong tất cả các trình duyệt hiện đại

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])
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.