+1

Tất cả những gì tôi biết về thẻ <script>

Bài viết được dịch từ nguồn: https://eager.io/blog/everything-I-know-about-the-script-tag/

Như các bạn đã biết, thẻ <script> được dùng để xác định mã JavaScript cần được chạy trên một trang web. Một thẻ script có thể bao gồm các đoạn mã JavaScript trực tiếp hoặc sẽ trỏ tới một url chứa mã JavaScript.

Các thẻ script được thực hiện theo thứ tự chúng xuất hiện

<script>
  var x = 3;
</script>
<script>
  alert(x);
  // Will alert '3';
</script>

Điều đó cũng tương tự với những tài nguyên bên ngoài

<script src="//typekit.com/fj3j1j2.js"></script>

<!-- Đoạn script thứ 2 sau đây sẽ không được thực hiện cho đến khi các mã script typekit bên trên được thực hiện xong, hoặc bị time out -->
<script src="//my.site/script.js"></script>

Trình tự thực hiện trên cũng đúng trong trường hợp chúng ta sử dụng kết hợp các tài nguyên local hoặc tài nguyên được tham chiếu từ địa chỉ khác.

Về mặt chức năng, điều này có nghĩa rằng bạn có thể làm chậm trang web của bạn đáng kể nếu nhưng bạn có những đoạn mã thực hiện chậm ở phần đầu trang web. Nó cũng có nghĩa rằng những đoạn mã xuất hiện ở phía cuối trang web có thể phụ thuộc vào những đoạn mã trước đó đã được thực hiện xong.

Các phần tử trên trang web sẽ không được thể hiện cho tới khi tất cả các thẻ script đứng trước đó đã được nạp và thực thi. Tức là là bạn có thể làm tất cả làm bất cứ việc gì trước khi trang web được tải nếu bạn sẵn sàng chấp nhận vấn đề hiệu năng thấp của trang web.

Thực tế điều này không được áp dụng, tuy nhiên nếu bạn thêm các thẻ script vào DOM sau khi trang bắt đầu tải bằng cách sử dụng document.appendChild hoặc cách nào đó tương tự. Những thẻ đó sẽ được tải bất cứ khi nào trình duyệt thấy phù hợp và không theo thứ tự nào đặc biệt.

Khi một thẻ script được thực thi, tất cả mọi thứ trước đó ở trong DOM đều đã có sẵn (Nhưng không phải mọi thứ sau đây)

<html>
  <head>
    <script>
      // document.head is available
      // document.body is not!
    </script>
  </head>
  <body>
    <script>
      // document.head is available
      // document.body is available
    </script>
  </body>
</html>

Bạn có thể nghĩ đến việc phân tích cú pháp HTML như việc di chuyển thông qua tài liệu từng thẻ một, thực hiện bất kỳ đoạn mã JavaScript nào gặp phải. Điều này có nghĩa các nút DOM đã có sẵn đối với JavaScript (thông qua các truy vẫn querySelectorAll, jQuery, ...) chỉ khi thẻ mở của chúng xuất hiện trong tài liệu sớm hơn thẻ script.

Một hệ quả hữu ích của việc này là document.head hầu như luôn luôn có sẵn trong bất kỳ JavaScript bạn có thể viết trên trang web. document.body chỉ có sẵn nếu thẻ script của bạn xuất hiện bên trong hoặc sau thẻ mở <body>

async and defer

HTML5 đã bổ sung thêm một cặp công cụ để kiểm soát khi các script được thực thi.

async nghĩa là "Thực hiện điều này bất cứ khi nào". Cụ thể hơn nghĩa là: "Tôi không quan tâm nếu bạn thực hiện điều này sau khi toàn bộ trang web đã được load, và toàn bộ các script đều đã thực thi". Nó rất hữu ích cho các mã phân tích theo dõi, ví dụ, nơi không có các mã khác trên trang web phụ thuộc vào việc thực thi của chúng. Định nghĩa một biến hay hàm trong mã async mà trang web cần là không tốt vì bạn không biết khi nào mã async sẽ thực sự chạy.

defer nghĩa là "Chờ cho phân tích cú pháp kết thúc để thực hiện điều này". Nó gần tương đương với ràng buộc script của bạn với sự kiện DOMContentLoaded, hoặc sử dụng jQuery.ready. Khi code được chạy, tất cả mọi thứ trong DOM sẽ có sẵn để sử dụng. Không giống như async, mã defer sẽ chạy theo thứ tự xuất hiện trong tài liệu HTML của trang web, nó chỉ hoãn lại cho đến sau khi HTML được phân tích đầy đủ.

Thuộc tính type

Lịch sử đã ghi nhận (từ khi có Netscape 2), không có vấn đề gì nếu bạn chỉ rõ type=”text/javascript” trong các thẻ script của mình, hoặc chỉ để trống nó. Nếu bạn thiết lập bất kỳ MIME type mặc dù nó không phải biến thể của JavaScript thì trình duyệt sẽ không thực hiện nó. Điều này sẽ thật tuyệt khi bạn muốn xác định ngôn ngữ của bạn

<script type="text/emerald">
  make a social network
    but for cats
</script>

Các hoạt động thực sự của mã code sau đó là tùy thuộc vào bạn

<script>
  var codez = document.querySelectorAll('script[type="text/emerald"]');
  for (var i=0; i < codez.length; i++)
    runEmeraldCode(codez[i].innerHTML);
</script>

Các bạn có thể tự tìm hiểu chức năng runEmeraldCode.

Nếu bạn thực sự mong muốn, bạn cũng có thể ghi đè lên các type mặc định dành cho tất cả các thẻ script trên trang sử dụng một thẻ meta.

<meta http-equiv="Content-Script-Type" content="text/vbscript">

Hoặc tiêu đề Content-Script-Type

Hãy xem “Tóm lược lịch sử của ngôn ngữ Weird Scripting" để biết thêm chi tiết về những types nào là hợp lệ.

Có một thuộc tính integrity?

Thuộc tính integrity là một phần của Subresource Integrity spec mới. Nó cho phép bạn cung cấp một hash cho các nội dung có thể nằm trong một file script. Điều này đồng nghĩa là để ngăn chặn một diễn biến xấu từ việc xáo trộng các nội dung của một thẻ script qua dây dẫn. Trong thế giới với SSL, điều này chỉ thực sự có giá trị nếu bạn đang tải một script từ một nguồn bên ngoài bạn không kiểm soát như code.jquery.com.

Nếu bạn chọn để sử dụng điều này, ban bao gồm hash bạn đang sử dụng, và hash này có giá trị, được mở rộng bằng một dấu gạch ngang. Nó trông như:

<script
  src="//code.jquery.com/jquery.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC">
</script>

Crossorigin cũng là 1 vấn đề!

Nó không được chuẩn hóa hoàn toàn, nhưng có một thuộc tính crossorigin được hỗ trợ bởi một số trình duyệt. Ý tưởng chung là trình duyệt không muốn để cho bạn làm nhiều với tài nguyên được tải từ một 'nguồn' khác với trang hiện tại. (Nguồn gốc được định nghĩa là sự kết hợp của các trang của giao thức, tên máy, và cổng. Tức là http://google.com:80).

Điều này để ngăn bạn khỏi việc tạo các yêu cần đến trang web cạnh tranh – nơi có thể hủy bỏ các tài khoản người dùng hiện có. Mặc dù sự kết nối của nó đến thẻ script có phần ngẫu nhiên. Ý tưởng là nếu bạn áp dụng một handler cho sự kiện window.onerror, mà handler được cung cấp một số thông tin về các trang và script mà bạn có lẽ không cần phải có nếu bạn đang tải mã từ một trang bên ngoài. Thông tin này không được bao gồm trong các trình duyệt bảo mật, , trừ khi bạn chỉ định crossorigin.

Mặc dù crossorigin không phải là một hack bảo mật nhiệm màu, những gì nó làm là hướng dẫn trình duyệt để trải qua việc kiểm tra quyền truy cập kiểm tra yêu cầu tại một OPTIONS của CORS bình thường và kiểm tra các tiêu đề Access-Control.

document.currentScript

Nó không hỗ trợ IE, tạo ra cho nó điều gì đó mới mẻ, nhưng có một thuộc tính được gọi là document.currentScript, có chỉ ra script hiện tại đang được thực hiện. Nó có thể là vô cùng hữu ích nếu bạn muốn kéo các thuộc tính ra khỏi thẻ script khi việc nhúng của bạn được bao gồm cả việc sử dụng. Cá nhân tôi khá vui vì nó không được hỗ trợ đầy đủ vì nó sẽ làm cho một số công việc chúng tôi làm tại Eager để cài đặt mã nhúng khó hơn.

onafterscriptexecute?!

Nó thực sự rất vô dụng, bởi chỉ hỗ trợ trên Firefox. Nó cùng với onbeforescriptexecute cho phép bạn ràng buộc một sự kiện mà sẽ được thực hiện trước và sau mỗi script trên trang chạy, mà là khá đẹp. Nếu bạn tò mò, các đối tượng sự kiện bao gồm một tham chiếu đến các kịch bản đang được thực thi, và các sự kiện before có thể hủy bỏ việc thực hiện bằng cách gọi preventDefault().

for / event

Cho đến ngày nay spec HTML5 bao gồm một phương pháp hiếm thấy (cụ thể là trước IE-specific) ràng buộc mã với một sự kiện. Bạn có thể làm điều này để có một thẻ script không chạy cho đến khi tải bắt đầu tải về.

<script for="window" event="onload">
  alert("Hi!")
</script>

Tôi thực sự không thể thực hiện việc này trên Chrome hoặc Firefox (làm cho chúng không phù hợp với tiêu chuẩn), nhưng với IE thì nó vẫn là một cơ hội tốt.

NOSCRIPT

Giống như cha mẹ của bạn, thật khó để tin vào khoảng thời gian khi JavaScript mới xuất hiện. Đã từng có khoảng thời gian, bạn không dám chắc rằng liệu trình duyệt được cung cấp có hỗ trợ JaveScript hay không. Thậm chí tệ hơn, bạn không chắc rằng trình duyệt có thể xác định cái gì là thẻ script. Và nếu một trình duyệt không nhận ra đâu là thẻ, có có chức năng biểu diễn như là một phần từ inline tổng quát, nghĩa là tất cả các JavaScript bí mật của bạn sẽ được biểu diễn trên một trang ở dạng chữ.

May mắn thay, spec đã phát huy tác dịnh trong việc đưa ra giải pháp, đóng gói mã code của bạn trong một thứ gì đó mà trình duyệt không hỗ trợ sẽ hiểu như là một nhận xét HTML

<script>
<!--  to hide script contents from old browsers

  // You would probably be pasting a ‘rollover’ script
  // you got from hotscripts.net here

// end hiding contents from old browsers  -->
</script>

Tất nhiên, giống như hầu hết mọi thứ, XHTML làm tệ hơn nhiều. XML có một phương pháp vô cùng đặc biệt để thoại khỏi nội cung có thể chứa các thẻ đống và theo cách đó, CDATA được hình thành.

<script>
//<![CDATA[

    // Is this the right incantation to get this to pass
    // the XHTML validator?

//]]>
</script>

Theo đó, mã của bạn sẽ là XHTML. Điều này sẽ không có bất kỳ tác động vào chức năng của nó, nhưng là vô cùng quan trọng để tự đánh giá bạn là một nhà phát triển web.

Các trình duyệt cũng bao gồm một phương pháp hữu ích cho phép bạn thông báo những người đã không kích hoạt Javascript.

<noscript> đóng gói các nội dung mà chỉ nên được thể hiện nếu trình duyệt không hỗ trợ thực hiện script

<noscript>
  Please use Internet Explorer 5.5 or above.
</noscript>
<script>
  exploitInternetExplorer0Day();
</script>

Nếu bạn tinh ý, bạn sẽ nhận ra rằng noscript không chấp nhận đối số type. Điều này khiến cho sự tương tác của nó với trang có sử dụng nhiều script types có gì đó hơi mơ hồ. Trên thực tế, hành vi có thay đổi từ trình duyệt đến trình duyệt, nhưng bao gồm việc thể hiện các khối noscript nếu bất kỳ thẻ scrpit được sử dụng sớm hơn trong tài liệu đã được dùng không phải là type được hỗ trợ. Điều này có nghĩa là rất có thể noscript không sớm xuất hiện, khi các noscript thấp hơn trên trang đã làm.

Script Tags and innerHTML

Các thẻ script được tự động thêm vào trang web thông qua DOM được thực thi bởi trình duyệt

var myScript = document.createElement('script');
myScript.textContent = 'alert("✋")';
document.head.appendChild(myScript);

Tuy nhiên điều đó lại không xảy ra với những thẻ script được tự động thêm vào trang web thông qua innerHTML

document.head.innerHTML += '<script>alert("✋")</script>';

Tại sao điều đó lại không rõ ràng, nhưng đó là một câu trả lời hài hước cho câu hỏi thách đố "Liệu có thể có một thẻ script hiển trị trong trình kiểm tra Chrome mà đã không thực sự chạy?". Bạn cũng có thể tận dụng điều này để thách đố, chơi khăm bạn của mình. 😄


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí