瀏覽器模型
瀏覽器環境概述
JavaScript 是瀏覽器的內置腳本語言。也就是說,瀏覽器內置了 JavaScript 引擎,並且提供各種接口,讓 JavaScript 腳本可以控制瀏覽器的各種功能。一旦網頁內嵌了 JavaScript 腳本,瀏覽器加載網頁,就會去執行腳本,從而達到操作瀏覽器的目的,實現網頁的各種動態效果。
這裏開始介紹瀏覽器提供的各種 JavaScript 接口。首先,介紹 JavaScript 代碼嵌入網頁的方法
代碼嵌入網頁的方法
網頁中嵌入 JavaScript 代碼,主要有四種方法:
script 元素嵌入代碼
<script>
var x = 1 + 5;
console.log(x);
</script>
1.text/javascript:這是默認值,也是歷史上一貫設定的值。如果你省略type屬性,默認就是這個值。對於老式瀏覽器,設爲這個值比較好
2.application/javascript:對於較新的瀏覽器,建議設爲這個值
由於
<script id="mydata" type="x-custom-data">
console.log('Hello World');
</script>
瀏覽器不會執行也不會顯示內容,因爲不認識它的type屬性;但是,這個
document.getElementById('mydata').text // console.log('Hello World');
script 元素加載外部腳本
<script src="https://www.example.com/script.js"></script>
如果腳本文件使用了非英語字符,還應該註明字符的編碼
<script charset="utf-8" src="https://www.example.com/script.js"></script>
所加載的腳本必須是純的 JavaScript 代碼,不能有HTML代碼和
<script charset="utf-8" src="example.js">
console.log('Hello World!');
</script>
爲了防止攻擊者篡改外部腳本,script標籤允許設置一個integrity屬性,寫入該外部腳本的 Hash 簽名,用來驗證腳本的一致性
<script src="/assets/application.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>
上面代碼中,script標籤有一個integrity屬性,指定了外部腳本/assets/application.js的 SHA256 簽名。一旦有人改了這個腳本,導致 SHA256 簽名不匹配,瀏覽器就會拒絕加載
事件屬性
網頁元素的事件屬性(比如onclick和onmouseover),可以寫入 JavaScript 代碼。當指定事件發生時,就會調用這些代碼
<button id="myBtn" onclick="console.log(this.id)">點擊</button> //如果有多個語句,使用分號分隔即可
URL 協議
URL 支持javascript:協議,即在 URL 的位置寫入代碼,使用這個 URL 的時候就會執行 JavaScript 代碼
<a href="javascript:console.log('Hello')">點擊</a>
瀏覽器的地址欄也可以執行javascript:協議。將javascript:console.log('Hello')放入地址欄,按回車鍵也會執行這段代碼;如果 JavaScript 代碼返回一個字符串,瀏覽器就會新建一個文檔,展示這個字符串的內容,原有文檔的內容都會消失
<a href="javascript: new Date().toLocaleTimeString();">點擊</a>
上面代碼中,用戶點擊鏈接以後,會打開一個新文檔,裏面有當前時間;如果返回的不是字符串,那麼瀏覽器不會新建文檔,也不會跳轉
<a href="javascript: console.log(new Date().toLocaleTimeString())">點擊</a>
javascript:協議的常見用途是書籤腳本 Bookmarklet。由於瀏覽器的書籤保存的是一個網址,所以javascript:網址也可以保存在裏面,用戶選擇這個書籤的時候,就會在當前頁面執行這個腳本。爲了防止書籤替換掉當前文檔,可以在腳本前加上void,或者在腳本最後加上void 0
<a href="javascript: void new Date().toLocaleTimeString();">點擊</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">點擊</a>
script 元素
工作原理
瀏覽器加載 JavaScript 腳本,主要通過
1.瀏覽器一邊下載 HTML 網頁,一邊開始解析;也就是說,不等到下載完,就開始解析
2.解析過程中,瀏覽器發現
3.如果
4.JavaScript 引擎執行完畢,控制權交還渲染引擎,恢復往下解析 HTML 網頁
加載外部腳本時,瀏覽器會暫停頁面渲染,等待腳本下載並執行完成後再繼續渲染;原因是 JavaScript 代碼可以修改 DOM,所以必須把控制權讓給它,否則會導致複雜的線程競賽的問題。如果外部腳本加載時間很長(一直無法完成下載),那麼瀏覽器就會一直等待腳本下載完成,造成網頁長時間失去響應,瀏覽器就會呈現“假死”狀態,這被稱爲“阻塞效應”。爲了避免這種情況,較好的做法是將
<head>
<script>
console.log(document.body.innerHTML);
</script>
</head>
<body></body>
上面代碼執行時會報錯,因爲此時document.body元素還未生成
一種解決方法是設定DOMContentLoaded事件的回調函數
<head>
<script>
document.addEventListener(
'DOMContentLoaded',
function (event) {
console.log(document.body.innerHTML);
}
);
</script>
</head>
DOMContentLoaded事件只有在 DOM 結構生成之後纔會觸發
另一種解決方法是使用
<script src="jquery.min.js" onload="console.log(document.body.innerHTML)"></script>
但是,如果將腳本放在頁面底部,就可以完全按照正常的方式寫,上面兩種方式都不需要。如果有多個script標籤,比如下面這樣:
<script src="a.js"></script>
<script src="b.js"></script>
瀏覽器會同時並行下載a.js和b.js,但是,執行時會保證先執行a.js,然後再執行b.js,即使後者先下載完成,也是如此。也就是說,腳本的執行順序由它們在頁面中的出現順序決定,這是爲了保證腳本之間的依賴關係不受到破壞。當然,加載這兩個腳本都會產生“阻塞效應”,必須等到它們都加載完成,瀏覽器纔會繼續頁面渲染。解析和執行 CSS,也會產生阻塞。Firefox 瀏覽器會等到腳本前面的所有樣式表,都下載並解析完,再執行腳本;Webkit則是一旦發現腳本引用了樣式,就會暫停執行腳本,等到樣式表下載並解析完,再恢復執行。
此外,對於來自同一個域名的資源,比如腳本文件、樣式表文件、圖片文件等,瀏覽器一般有限制,同時最多下載6~20個資源,即最多同時打開的 TCP 連接有限制,這是爲了防止對服務器造成太大壓力。如果是來自不同域名的資源,就沒有這個限制。所以,通常把靜態文件放在不同的域名之下,以加快下載速度
defer 屬性
爲了解決腳本文件下載阻塞網頁渲染的問題,一個方法是對
<script src="a.js" defer></script>
<script src="b.js" defer></script>
上面代碼中,只有等到 DOM 加載完成後,纔會執行a.js和b.js
defer屬性的運行流程如下:
1.瀏覽器開始解析 HTML 網頁
2.解析過程中,發現帶有defer屬性的
3.瀏覽器繼續往下解析 HTML 網頁,同時並行下載
4.瀏覽器完成解析 HTML 網頁,此時再回過頭執行已經下載完成的腳本
有了defer屬性,瀏覽器下載腳本文件的時候,不會阻塞頁面渲染。下載的腳本文件在DOMContentLoaded事件觸發前執行(即剛剛讀取完