JavaScript是一種描述型的腳本語言,是一種解析語言,由瀏覽器動態解析,不同類型的瀏覽器、不同版本的瀏覽器對于JavaScript的解析有著微小的差別,不同瀏覽器的JavaScript解析引擎效率也有差異。
JavaScript的執行過程分為兩大部分:
總的來說,JavaScript的執行分為兩部分:解析過程和執行過程。解析時按照代碼塊,一段一段進行解析,執行時按照代碼塊順序逐行執行,解析一個代碼塊,執行一個代碼塊。
因為是解釋型語言,所以JavaScript如果在解析過程中有錯誤,則不會提示,也可以理解為JavaScript不會出現編譯錯誤,但如果出現了運行時錯誤,出現錯誤以下的所有JavaScript代碼將不會繼續執行。
預處理:創建一個詞法環境(LexicalEnvironment,簡寫為LE),掃描JavaScript中用聲明的方式聲明的函數,用var定義的變量并將它們加到預處理階段的詞法環境中去。
預處理階段先讀取代碼塊,不是一行一行的解析執行定義的方法和用var定義的變量,會放到一個(不同的環境,會有對應的詞法環境)詞法環境環境中。
var a=1; // 用var定義的變量,已賦值
var b; // 用var定義的變量,未賦值
c=3; // 未定義的變量,直接賦值
// 用聲明的方式聲明的函數
function d(){
console.log('hello');
}
// 函數表達式
var e=function() {
console.log('world');
}
詞法環境:
LE { // 此時的LE相當于window
a: undefined
b: undefined
d: 函數引用
e: undefined
}
預處理的函數必須是JavaScript中用聲明的方式聲明的函數,不是函數表達式。
示例:
d();
e();
// 用聲明的方式聲明的函數
function d(){
console.log('hello');
}
// 函數表達式
var e=function() {
console.log('world');
}
執行結果:
hello
TypeError: e is not a function
函數優先原則:在既有函數聲明又有變量聲明的時候,函數聲明的權重高于變量聲明,所以最終結果往往是指向函數的引用。
示例 1:
console.log(f);
var f=1;
function f() {
console.log('func');
}
結果:
[Function: f]
示例 2:
console.log(f);
function f() {
console.log('func');
}
var f=1;
結果:
[Function: f]
console.log(a); // undefined
console.log(b); // TypeError: b is not a function
console.log(c); // [Function: f]
console.log(d); // undefined
var a=1;
b=2;
console.log(b); // 2
function c(){
console.log('c');
}
var d=function(){
console.log('d');
}
console.log(d); // [Function: f]
詞法環境:
LE {
a: undefined
c: [Function: f]
d: undefined
}
預處理階段傳輸參數值一一對應
function func(a, b) {
console.log(a);
console.log(b);
var b=100;
function a{}
}
func(1, 2);
詞法環境:
LE {
b: 2
a: 指向函數的引用
arguments: 2 // 調用函數時實際調用的參數個數
}
運行結果:
[Function: f]
2
沒有用var聲明的變量,會變成最外部LE的成員,即全局變量:
用代碼分割、延遲加載、使用 Web Workers、壓縮文件和異步加載等技術提升您的 JavaScript 技能。
譯自 How To Master JavaScript Performance Optimization,作者 Alexander T Williams。
JavaScript 是現代 Web 應用程序的基石,為從動態內容到交互式功能的一切提供支持。然而,隨著應用程序變得越來越復雜,確保 JavaScript 能夠高效運行變得至關重要。
隨著用戶對更快、更具響應性的應用程序的需求不斷增長,開發人員必須優先考慮 JavaScript 優化以滿足這些期望。從減少加載時間到提高交互性,優化您的 JavaScript 可以顯著提高 Web 應用程序的整體性能。
正確理解網站的性能是優化 JavaScript 代碼的第一步。
考慮到這一點,衡量您的網站或應用程序的性能至關重要,因為它可以幫助您識別影響下載時間、渲染速度和整體用戶體驗的瓶頸。
如果沒有對性能進行適當的衡量,您可能會浪費時間應用優化,而這些優化并不能解決您的網站所面臨的實際問題。
有幾種工具可以幫助您有效地衡量性能。內置的瀏覽器工具,例如 Chrome DevTools,提供關于網絡活動、加載時間和 CPU 使用率的全面而有價值的見解。
收集完性能數據后,下一步是確定哪些優化是必要的。
這些工具可以讓您看到頁面中哪些部分加載時間最長,以及哪些腳本可能會減慢網站速度。除此之外,性能 API 還可以提供更復雜的數據,用于深入分析。
收集完性能數據后,下一步是確定哪些優化是必要的。并非每種技術都適合每個項目,因此根據您網站的具體需求進行優先排序非常重要。
例如,如果您的分析表明事件處理程序會導致延遲,您可以專注于改進事件管理。類似地,如果大型 JavaScript 文件會減慢加載時間,縮小和異步加載可能是正確的解決方案。
此外,它還可以幫助您遵守 GDPR,或與您的網站或應用程序相關的歐盟、美國或其他地方的任何數據保護法規。優化您的 JavaScript 有助于提高性能,同時確保您的數據處理實踐符合標準。
正確管理的代碼可以幫助最大限度地減少不必要數據的收集,從而簡化嘗試遵守和遵循重要監管要求的過程。
我們都經歷過:如果您的代碼沒有得到妥善管理,JavaScript 有時會成為一個真正的頭痛問題。
您可能遇到的一些常見問題包括質量較差的事件處理,這會導致深層調用堆棧和更慢的性能。無序的代碼是另一個大問題,會導致資源分配效率低下,并使瀏覽器更難快速執行腳本。
代碼拆分允許您將 JavaScript 代碼分解成更小、更易于管理的塊。
然后是過度依賴的問題,這會減慢應用程序的速度,通常會顯著減慢速度,尤其是對于帶寬有限的移動用戶而言——而且不要忘記,低效的迭代會不必要地拖延處理時間。
代碼拆分允許您將 JavaScript 代碼分解成更小、更易于管理的塊——這在您的應用程序變得越來越復雜時至關重要,有助于減少加載時間并提高用戶的初始渲染速度。
那么,如何進行代碼拆分呢?一種常用的方法是使用動態導入,它允許您僅在需要時加載 JavaScript 模塊,而不是一次性將整個應用程序加載到用戶身上。這就像只為周末旅行打包必需品,而不是打包整個衣櫥。
根據最近的調查統計,48.9% 的開發人員已采用動態導入按需加載模塊,45.7% 的開發人員正在使用服務工作者 來增強離線用戶體驗。
同樣,對于 JS 庫也是如此,允許進行各種應用內操作,例如在 React 應用中查看文檔,動態在實時分析儀表板中渲染圖表,或加載交互式地圖以用于基于位置的服務。然后是 webpack,一個工具,一旦你掌握了它,就會感覺有點像魔法;它可以自動將你的代碼拆分成更小的塊,按需加載它們。
import('./module.js').then(module=> {
module.doSomething();
});
const MyComponent=React.lazy(()=> import('./MyComponent'));
延遲加載是一種很棒的技術,可以通過延遲加載非必要資源來提高 Web 應用的性能,直到它們真正需要時才加載。
簡而言之,延遲加載允許這些元素僅在進入用戶的視野時加載,而不是讓用戶等待每個圖像、視頻或媒體文件預先加載。
延遲加載最常見的用例包括圖像、視頻和其他媒體密集型內容等元素。使用延遲加載可以大幅減少初始加載時間,從而增強網站或應用的整體用戶體驗。
實現延遲加載的一種流行方法是通過 Intersection Observer API。這個特定的 API 允許你檢測元素何時進入或退出視窗,因此你可以在內容即將對用戶可見時才加載它。它效率高且設置起來相對容易。
const observer=new IntersectionObserver((entries)=> {
entries.forEach(entry=> {
if (entry.isIntersecting) {
loadImage(entry.target);
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img=> observer.observe(img));
Web Workers 是現代 Web 開發中的一項強大功能,旨在幫助處理繁重的計算,而不會減慢用戶界面。
Web Workers 從主線程卸載密集型任務,通過在后臺線程中運行腳本,提供流暢且響應迅速的用戶體驗。
Web Workers 通過啟用并行執行來顯著提高性能;因此,當主線程處理用戶交互和渲染時,Web Workers 負責后臺的資源密集型操作,例如數據處理和計算。這可以防止 UI 由于長時間運行的腳本而變得無響應。
使用 Web Workers 的一些更實際的示例包括卸載基本數據處理任務。例如,當處理需要排序、過濾或復雜計算的大型數據集時,Web Worker 可以管理這些操作,而不會凍結主 UI 線程。
// worker.js
self.onmessage=(e)=> {
const result=computeHeavyTask(e.data);
postMessage(result);
};
const worker=new Worker('worker.js');
worker.onmessage=(e)=> {
console.log('Result from worker:', e.data);
};
worker.postMessage(data);
優化 JavaScript 不僅僅是代碼分割和延遲加載,還有其他一些技術可以顯著提高應用程序的性能。
異步加載允許腳本與其他資源并行獲取。
一種重要的方法是 壓縮和壓縮 JavaScript 文件,這涉及從代碼中刪除不必要的字符和空格,而不會改變其功能。像 UglifyJS 這樣的工具可以幫助完成此過程,使用 gzip 或 Brotli 壓縮可以進一步減小文件大小,從而加快加載時間。
另一方面,異步加載允許腳本 與其他資源并行獲取,防止它們阻塞頁面的渲染。HTML 中的 async 屬性通常用于此目的。
使用 defer 屬性延遲腳本,確保 代碼在初始 HTML 解析后執行,這提高了用戶與網站交互的速度。
利用 HTTP/2 和 JavaScript CDN 可以進一步提高網站或應用程序的性能。
HTTP/2 引入了多路復用等功能,允許多個請求同時通過單個連接發送,從而減少延遲。使用 內容交付網絡 (CDN) 為您的 JavaScript 文件提供服務 可以保證它們從更靠近用戶的位置提供服務,從而加快交付速度。
代碼分割、延遲加載、使用 Web Workers、壓縮文件和利用異步加載等技術并不完全是秘密,但開發人員并沒有充分利用它們——遠非如此。
每種方法都可以提高應用程序的速度和響應能力,將它們納入開發工作流程將提供更流暢的用戶體驗,并使您的應用程序保持領先地位。
了執行Javascript,需要在HTML文件內以特定的方式書寫JavaScript的代碼,JavaScript的書寫方法有多種,其執行的流程也各不相同:
此種嵌入方法無法操作<script>之后的DOM元素。因為<script>之后的DOM元素還未構造,因此在<script>標簽內就無法取得位于其后的DOM元素。
此種嵌入方法可以指定defer、async屬性。defer可以推遲執行,async可以異步執行。
此種嵌入方法在頁面讀取完后再對其執行,所以可以對所有的DOM元素操作。
<body onload="alert('hello')">
window.onload=function(){alert('hello');};
當window.onload事件觸發時,頁面上所有的DOM、樣式表、腳本、圖片、flash都已經加載完成了。
//window.onload不能同時編寫多個。
//以下代碼無法正確執行,結果只輸出第二個。
window.onload=function(){
alert("test1");
};
window.onload=function(){
alert("test2");
};
//$(document).ready()能同時編寫多個
//結果兩次都輸出
$(document).ready(function(){
alert("Hello World");
});
$(document).ready(function(){
alert("Hello again");
});
window.onload和body中onload也有些許區別:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.0.js"></script>
<script language="javascript">
window.onload=haha;
function haha(){console.log("window.onload");}
if(document.addEventListener){
function DOMContentLoaded(){
console.log("DOMContentLoaded");
}
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
}</script>
</head>
<body onload="console.log('bodyonload');">
<div id="div1">a</div>
</body>
</html>
在IE10和FireFox下,結果為 :
"DOMContentLoaded"
"bodyonload"
說明body中的onload會覆蓋window.onload
在chrome下,結果為:
DOMContentLoaded
window.onload
bodyonload
然后,如果把javascript代碼移到最下面,結果又會是什么樣呢?
chrome和IE10、FireFox的結果竟然是一樣的:
DOMContentLoaded
window.onload
IE 10、Fire Fox可以理解,window.on load和body中的 on load 誰在下面就是誰覆蓋誰,只會執行后面的那個。
onload方法可能需要等待時間,而本方法可以在完成HTML解析后發生的事件,減少等待時間。
在chrome、IE10和FireFox中,執行結果是:DOMContentLoaded然后才是onload的輸出。所以說一般情況下,DOMContentLoaded事件要在window.onload之前執行,當DOM樹構建完成的時候就會執行DOMContentLoaded事件。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript" src="jquery2.js"></script>
<script language="javascript">
window.onload=haha;
function haha(){console.log(document.getElementById("div1"));}
if(document.addEventListener){
function DOMContentLoaded(){
console.log("DOMContentLoaded");
}
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
}
</script>
</head>
<body>
<div id="div1">a</div>
</body>
</html>
如果你是個jQuery使用者,你可能會經常使用$(document).ready();或者$(function(){}),這都是使用了DOMContentLoaded事件
5.1 使用原生js方法
動態創建script標簽,并指定script的src屬性
function loadJs(url, callback) {
var script=document.createElement('script');
script.type="text/javascript";
if (typeof(callback) !="undefined") {
if (script.readyState) {
script.onreadystatechange=function() {
if (script.readyState=="loaded" || script.readyState=="complete") {
script.onreadystatechange=null;
callback();
}
}
} else {
script.onload=function() {
callback();
}
}
}
script.src=url;
document.body.appendChild(script);
}
loadJs("test.js", function() {
alert('done');
});
還可以使用同樣的原理動態加載css文件,只不過插入的的父節點是head標簽。
5.2 使用document.write/writeln()方式
該種方式可以實現js文件的動態加載,原理就是在重寫文檔流,這種方式會導致整個頁面重繪。
document.writeln("<script src=\"http://lib.sinaapp.com/js/jquery/1.6/jquery.min.js\"></script>");
需要注意的是特殊字符的轉義。
5.3 使用jQuery
使用getScript(url,callback)方法實現動態加載js文件
$.getScript('test.js',function(){
alert('done');
});
-End-
*請認真填寫需求信息,我們會在24小時內與您取得聯系。