Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
載說明:原創不易,未經授權,謝絕任何形式的轉載
在當今充滿活力的網絡開發領域中,實現強大的搜索功能是一個關鍵特性,可以極大地增強用戶體驗,并使瀏覽大型數據集變得輕松自如。如果您想要為您的網站或網絡應用程序添加實時搜索功能,那么您來對地方了。本篇全面的文章將探討使用JavaScript實現實時搜索功能的方方面面。
無論您是經驗豐富的開發人員還是剛開始編碼之旅的新手,本文旨在為您提供一般編碼知識和工具,以便將實時搜索功能融入到您的項目中。通過本指南的學習,您將對相關概念和技術有扎實的理解,從而能夠創建響應式和交互式的搜索功能,實現用戶輸入時動態更新的效果。
為了有效地跟隨本指南,建議您對HTML、CSS和JavaScript的基本知識有扎實的理解。熟悉DOM操作和事件處理將有助于我們深入了解實現細節。然而,即使您對JavaScript或Web開發相對較新,本指南的結構也旨在提供清晰的解釋和逐步的說明,使其適用于不同技能水平的學習者。
現在,為了更好地理解這個功能的重要性和使用方法,我們將創建一個非常基本的項目作為示例;更具體地說,一個如下所示的電影應用程序:
您可以在這里查看實時實施情況。
https://search-movies-live.netlify.app/
在這個項目中,我們將利用實時搜索功能來搜索電影數據庫中的電影列表。我知道你迫不及待地想要開始了,我們馬上就會開始。但首先,讓我們更多地了解一下實時搜索功能及其重要性。
在當今數字化的環境中,實時搜索功能變得至關重要,滿足了高效信息檢索的需求,提升了整體用戶體驗。通過在用戶輸入時實時更新搜索結果,實時搜索提供即時反饋,便于快速獲取相關信息。這種動態交互式的搜索功能帶來了許多好處,使用戶和網站所有者受益。
既然我們已經完全了解了實時搜索功能以及它的重要性,那么讓我們深入探討一下如何在您自己的項目中實現這個功能。
首先,讓我們建立項目的結構。對于這個項目,我們只需要三個文件,即HTML、CSS和JavaScript文件。
現在讓我們開始設置項目的HTML結構:在HTML文件中,我們首先需要包含我們的標準HTML樣板,包括鏈接和腳本到我們的CSS和JS文件中:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./live-search.css" />
<title> </title>
</head>
<body>
<script src="./live-search.js"></script>
</body>
</html>
現在在 body 標簽中,我們包含了 header 和 main 語義標簽。在 header 標簽內,我們設置了項目的標題部分,這里只包括應用程序的名稱和一個視頻圖標。
<header>
<ion-icon name="videocam"></ion-icon>
<h1>Search Movies</h1>
</header>
在我們繼續講解 main 標簽之前,在 body 標簽的末尾,讓我們包含所需的 script 標簽,以便能夠使用這些圖標:
<script
type="module"
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
></script>
<script
nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
></script>
您可以在Ionicons網站上找到這些圖標。
https://ionic.io/ionicons
現在,在 main 標簽內,我們將包含我們的第一個 div 標簽,這將是我們的搜索欄容器,在其中,我們放置我們的搜索輸入標簽和一個搜索圖標:
<div id="search-container">
<ion-icon name="search-outline"></ion-icon>
<input type="search" id="search-bar" placeholder="Search movies..." />
</div>
然后,我們將在這個“div”下面創建另一個 div 標簽。這將包含所有電影數據的結果:
<div id="results-container"></div>
我們暫時將其留空,因為其內容將在JavaScript部分生成。
最后,在 main 標簽中,我們將包含一個 p 標簽。這個標簽只是為了在稍后向用戶顯示錯誤或空消息的響應。
<p id="movie-unavailable-txt"></p>
這就是HTML文件的全部內容,整體代碼應該是這樣的:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./live-search.css" />
<title>Live Search Functionality</title>
</head>
<body>
<header>
<ion-icon name="videocam"></ion-icon>
<h1>Search Movies</h1>
</header>
<main>
<div id="search-container">
<ion-icon name="search-outline"></ion-icon>
<input type="search" id="search-bar" placeholder="Search movies..." />
</div>
<div id="results-container"></div>
<p id="movie-unavailable-txt"></p>
</main>
<script src="./live-search.js"></script>
<script
type="module"
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
></script>
<script
nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
></script>
</body>
</html>
既然我們已經完成了項目的HTML結構的實現,那么讓我們給頁面添加一些樣式。
在這個部分,我們將為頁面的各個部分添加基本的樣式。所以讓我們開始吧。首先,讓我們為頁面的整體部分添加一些常規樣式:
html{
scroll-behavior: smooth;
background-color: #111;
color: whitesmoke;
}
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
現在讓我們為頭部部分及其內容添加一些樣式:
header{
display: flex;
justify-content: center;
padding: 25px;
letter-spacing: 2px;
position: sticky;
top: 0%;
z-index: 2;
border-bottom: 2px solid ;
background-color: black;
text-shadow: 3px 3px 5px #fd1d6b;
box-shadow: 10px 10px 20px -10px #fd1d6b;
}
header > ion-icon{
color:#fd1d6b;
font-size: 60px;
position: absolute;
left: 5%;
}
接下來,我們開始對搜索容器及其內容進行樣式設置:
#search-container{
display: flex;
justify-content: center;
padding: 20px;
margin-bottom: 20px;
position: sticky;
top: 100px;
}
#search-bar{
border: none;
width: 60%;
padding: 15px;
padding-left: 40px;
border-radius: 15px;
font-size: 15px;
}
#search-container>ion-icon{
color: gray;
position: relative;
left: 30px;
top: 13px;
z-index: 3;
font-size: 19px;
}
之后,我們繼續設計將來從電影數據庫中獲取的所有電影的 results-container 的樣式
#results-container{
border-right: 5px solid #fd1d6b;
border-left: 5px solid #fd1d6b;
border-radius: 25px;
display: flex;
justify-content: center;
flex-wrap: wrap;
width: 90vw;
}
接下來,我們在 movie-unavailable-txt 中添加樣式,同時將 display 設置為 none ,因為我們暫時不希望它可見
#movie-unavailable-txt{
text-align: center;
letter-spacing: 2px;
display: none;
margin-top: 15%;
text-shadow: 3px 3px 5px #fd1d6b;
}
接下來,我們將為一些尚未聲明但將通過JavaScript創建的元素添加一些樣式。這是電影卡片,將顯示電影的詳細信息,包括電影圖片和標題:
.movie-cards{
padding: 25px;
max-width: 250px;
border-radius: 15px;
display: grid;
place-items: center;
box-shadow: 1px 1px 20px -1px #fd1d6b ;
margin: 50px;
}
.title{
margin: 20px auto;
text-align: center;
font-size: 1.2rem;
text-shadow: 3px 3px 5px #fd1d6b;
}
.date{
margin-top: 15px;
font-size: 0.8rem;
text-shadow: 3px 3px 5px #fd1d6b;
}
.movie-image{
width: 90%;
max-width: 400px;
object-fit: contain;
border-radius: 5px;
}
既然我們已經完成了頁面的樣式,那么讓我們繼續進行最有趣和最重要的部分,即Javascript的實現。
在本節中,我們將調用我們選擇的電影數據庫API來填充我們的頁面,展示各種電影。在這種情況下,我將使用RapidAPI Hub中的IMDb Top 100 Movies免費電影API。在API頁面中,我們選擇要使用的特定數據,然后復制頁面右側提供的javascript(fetch)代碼,如下所示:
https://rapidapi.com/rapihub-rapihub-default/api/imdb-top-100-movies
首先,在您使用API之前,請先訂閱它(無需信用卡),以便為您生成一個API密鑰。您可以在API的定價頁面上進行訂閱。
繼續,我們進入我們的空的JavaScript文件,并將我們復制的代碼粘貼進去:
const url = "https://imdb-top-100-movies.p.rapidapi.com/";
const options = {
method: "GET",
headers: {
"X-RapidAPI-Key": "YOUR GENERATED API KEY",
"X-RapidAPI-Host": "imdb-top-100-movies.p.rapidapi.com",
},
};
try {
const response = await fetch(url, options);
const result = await response.text();
console.log(result);
} catch (error) {
console.error(error);
}
既然我們已經將電影數據庫的API引入到我們的項目中,我們可以使用它的數據了。接下來,我們將聲明一些我們需要的變量,并將它們放在我們復制的代碼中 try 塊的上方。
const searchBar = document.getElementById("search-bar");
const resultsContainer = document.getElementById("results-container");
const movieUnavailableTxt = document.getElementById("movie-unavailable-txt");
let movieList;
let searchValue;
let searchedMovies;
我們正在接近剛剛創建的這些變量的目的,堅持住。
接下來,我們對復制的代碼中的 try 塊進行一些更改,因為我們希望將其與我們的項目完全集成。所以首先,我們需要創建一個異步函數:
const fetchMovies = async () => {
// try catch block goes in here.
};
在這個函數內部,我們將放置從我們復制的代碼中的整個 try catch 塊,以便我們可以進行異步API調用。
在 try 塊內,我們將刪除 console.log(result) 行,并將 result 變量更改為先前聲明的 movieList 變量,并將同一行中的 response.text() 更改為 response.json() 。這樣,我們從API調用中接收到的數據將以 JSON 格式呈現,這是我們所需的。因此,該行現在應該是這樣的
movieList = await response.json();
現在我們已經成功從API中獲取了電影并返回了我們的數據集,我們需要將這些數據填充到我們的頁面中。為此,我們將調用 renderMovies() 函數,并將參數設置為從API調用中獲取的數據。不用擔心,我們很快就會創建這個函數:
renderMovies(movieList);
現在讓我們創建 renderMovies 函數,這個函數剛剛在 fetchMovies() 函數中調用過,這個函數將用于創建動態電影卡片模板,我們之前在CSS文件中設置了樣式,模板中的每個元素的內容都將設置為從API獲取的數據,這樣我們就可以使用相同的模板渲染不同的電影。然后,我們將把電影卡片放在 resultsContainer 元素中。每次調用函數時,我們需要清除 resultsContainer ,并將 moviesUnavailableTxt 設置為 display="none" ,因為我們希望在渲染電影到頁面時文本不可見,同時清除 moviesReturnedOnSearch 數組,然后將從搜索輸入字段返回的新數據設置到其中。
const renderMovies = (movies) => {
resultsContainer.innerHTML = ""; // Clear the existing movies
movieUnavailableTxt.style.display = "none"; // Hide the "No movies found" message
moviesReturnedOnSearch = []; // Clear the movies returned on search array
movies.forEach((movie) => {
resultsContainer.innerHTML += `
<div class="movie-cards">
<img src="${movie.image}" alt="movie image" class="movie-image" />
<h2 class="title">${movie.title}</h2>
<p class="plot">${movie.description}</p>
<p class="date">${movie.year}</p>
</div>
`;
moviesReturnedOnSearch.push(movie); // Add the movies that are a result to the search input value
});
};
現在我們已經將所有電影數據加載到我們的頁面上,接下來是真正有趣的部分,我們要實現實時搜索功能,所以,不浪費時間,讓我們開始吧。
為了捕獲用戶輸入,我們將使用 input 事件監聽器,并將其鏈接到 searchBar 元素。我們使用這個特定的事件監聽器,因為它可以捕獲搜索框內的每一個活動,包括輸入、清除和粘貼,這正是我們想要的。所以讓我們繼續創建它:
searchBar.addEventListener("input", (event) => {
// live functionality code
});
好的,現在我們已經將事件監聽器鏈接到搜索欄,以便監聽用戶的任何輸入。在第二個參數中,我們添加了事件處理程序,這是每當搜索欄有輸入時將被調用的函數。現在,在該函數內部,我們將編寫處理實時搜索的代碼。
在搜索功能中,我們需要做的第一件事是編輯從用戶那里獲取到的輸入值,并將其轉換為全小寫,同時去除任何不必要的空格:
searchValue = event.target.value.trim().toLowerCase();
在那之后,我們繼續根據用戶的搜索輸入,通過檢查用戶輸入的電影標題是否包含在 movieList 數據中的任何電影標題中,并將電影標題設置為小寫以與用戶輸入匹配,來在頁面上按標題篩選電影
const filteredMovies = movieList.filter( (movie) => movie.title.toLowerCase().includes(searchValue) );
接下來,我們將通過再次調用 renderMovies() 函數,并將參數設置為 filtered Movies 變量的值,來顯示與用戶在搜索欄中輸入的字符匹配的電影標題的實時搜索結果。
renderMovies(filteredMovies);
通過調用此函數,它僅將與搜索欄中鍵入的字符匹配的電影渲染到頁面上,使用函數中提供的電影卡片模板,同時將每個匹配的電影添加到 moviesReturnedOnSearch 數組中,以便我們可以跟蹤每個字符輸入的匹配搜索值的電影數量。在處理空響應錯誤時,這將非常有用,現在我們將進入這個部分。
在任何應用程序中,有效處理空或錯誤的響應至關重要。在這種情況下,這些情景可能發生在搜索查詢沒有結果或API請求存在問題時。
處理錯誤或空響應時,向用戶提供清晰的反饋是至關重要的。話雖如此,由于這是一個相對簡單的應用程序,我們不必過多擔心錯誤,因為我們只需要處理由API引起的錯誤。例如,API服務可能暫時不可用,或者應用程序可能剛剛超過了請求限制。為了處理這個錯誤,我們只需要將 movieUnavailableTxt 元素的 display 設置為 block ,并將 innerHTML 設置為向用戶顯示錯誤消息,并將其放置在 fetchMovies() 函數的 catch 塊中。因此,現在 catch 塊的代碼如下所示:
catch (error) {
movieUnavailableTxt.innerHTML = 'An error occurred while fetching movies. <br /> Please try again later.';
movieUnavailableTxt.style.display = "block";
console.error(error);
}
現在我們已經處理完錯誤響應,接下來我們要處理空響應。如果用戶搜索的電影與頁面上的任何電影都不匹配,我們需要向用戶提示所搜索的電影不可用。為了做到這一點,首先我們需要檢查之前聲明的 moviesReturnedOnSearch 數組的內容,如果數組的長度小于或等于0,我們將設置 movieUnavailableTxt 元素的 display 為 block ,并將 innerHTML 設置為空響應消息,如下所示:
if (moviesReturnedOnSearch.length <= 0) {
movieUnavailableTxt.innerHTML = "OOPS! <br/><br/> Movie not available";
movieUnavailableTxt.style.display = "block"; // Show the "No movies found" message if no movies match the search
}
我們將把這個 if 塊放在 searchBar 事件處理程序的閉合括號之前。
在使用API實現實時搜索功能時,提高性能的一種有效技術是緩存。緩存涉及存儲先前獲取的搜索結果,并在再次請求相同的搜索查詢時重復使用它們。這可以顯著減少API調用的次數,有助于防止超過API的請求限制,并改善搜索功能的響應速度以及網站的加載時間。
要在我們的項目中實現緩存,首先,我們需要確定哪些項目需要被緩存,而在這種情況下,那將是 movieList 變量的值,它是我們從 fetch API請求中得到的 JSON 格式的數據。通過緩存這個項目,我們將能夠在頁面重新加載時使用API的數據,而無需進行額外的 fetch 請求。但是對于這個項目,我們將為我們的緩存數據設置一個過期時間,為6小時,這意味著頁面每6小時只會進行一次API請求,而不是在每次頁面重新加載時都進行請求。這樣做是為了保持頁面的數據新鮮和最新,同時將API請求保持在最低限度。
回到我們的代碼中,現在我們需要將數據存儲在瀏覽器的本地存儲中,但為了做到這一點,我們需要首先將其轉換為一個 string ,并設置一個鍵名,用于在本地存儲中標識數據。讓我們將其設置為 movieData ,如下所示:
localStorage.setItem("moviedata", JSON.stringify(movieList));
接下來我們需要將當前日期和時間存儲在本地存儲中:
localStorage.setItem("cacheTimestamp", Date.now());
這將當前日期和時間以毫秒為單位存儲,鍵名為 cacheTimeStamp 。
我們將把這兩行代碼放在 try 塊的 fetchMovies() 函數中,就在 movieList 變量的下方。
接下來,在 fetchMovies() 函數之外,緊接著 renderMovies() 函數的下方,我們將把緩存數據的過期時間設置為6小時(以毫秒為單位)
const expirationDuration = 21600000; // 6 hours in milliseconds
之后,我們需要取回之前在本地存儲中設置的 cacheTimestamp :
const cacheTimestamp = localStorage.getItem("cacheTimestamp");
現在,我們將檢查緩存數據是否已過期或不可用,這意味著它尚未被存儲。如果是這種情況,我們將通過調用 fetch 函數向API發出新的請求。另一方面,如果緩存數據存在且尚未過期,我們將使用它來渲染頁面上的電影,而不是再次發出新的 fetch 請求。我們通過檢索緩存的電影數據并將其解析回 JSON 格式來使用,然后將參數設置為從緩存中獲取的數據,調用 render 函數來實現這一點。
// Check if cache has expired or data is not available
if (
!cacheTimestamp ||
Date.now() - parseInt(cacheTimestamp) > expirationDuration
) {
// Cache expired or data not available, fetch movies again
fetchMovies();
} else {
// Use cached movie data
movieList = JSON.parse(localStorage.getItem("moviedata"));
renderMovies(movieList);
}
在 if 語句中, !cacheTimestamp 檢查 cacheTimestamp 變量是否為假值,意味著它可以是 null 、 undefined 、0、 false 或空字符串。如果 cacheTimestamp 為假,表示沒有存儲現有緩存時間戳。 Date.now() - parseInt(cacheTimestamp) 計算當前時間戳與解析的 cacheTimestamp 的整數值之間的時間差。簡單來說,這就是說:“當前時間的值減去我們之前存儲在緩存中的時間的值,是否大于我們設置的過期時間?如果是,就從API中重新獲取電影數據;如果不是,就使用緩存的數據。”
就是這樣,這就是我們如何將數據緩存起來以便重復使用,而不是在每次用戶輸入或每次頁面重新加載時發起請求。正如你所看到的,這將極大地優化應用程序的性能,因為它可以防止由于網絡慢而導致的電影渲染緩慢。
我們已經完成了我們的小電影應用程序中展示實時搜索功能的所有特性的實現。以下是該應用程序的整體JavaScript代碼:
const url = "https://imdb-top-100-movies.p.rapidapi.com/";
const options = {
method: "GET",
headers: {
"X-RapidAPI-Key": "Your Generated API Key",
"X-RapidAPI-Host": "imdb-top-100-movies.p.rapidapi.com",
},
};
const searchBar = document.getElementById("search-bar");
const resultsContainer = document.getElementById("results-container");
const movieUnavailableTxt = document.getElementById("movie-unavailable-txt");
let movieList;
let searchValue;
let moviesReturnedOnSearch;
// Function to fetch movies from the API
const fetchMovies = async () => {
try {
const response = await fetch(url, options);
movieList = await response.json();
// Storing the Movie Data in browser storage
localStorage.setItem("moviedata", JSON.stringify(movieList));
localStorage.setItem("cacheTimestamp", Date.now()); // Update cache timestamp
// Render the movies on the page
renderMovies(movieList);
} catch (error) {
movieUnavailableTxt.innerHTML =
"An error occurred while fetching movies. <br /> Please try again later.";
movieUnavailableTxt.style.display = "block";
console.error(error);
}
};
// Function to render movies on the page
const renderMovies = (movies) => {
resultsContainer.innerHTML = ""; // Clear the existing movies
movieUnavailableTxt.style.display = "none"; // Hide the "No movies found" message
moviesReturnedOnSearch = []; // Clear the movies returned on search array
movies.forEach((movie) => {
resultsContainer.innerHTML += `
<div class="movie-cards">
<img src="${movie.image}" alt="movie image" class="movie-image" />
<h2 class="title">${movie.title}</h2>
<p class="plot">${movie.description}</p>
<p class="date">${movie.year}</p>
</div>
`;
moviesReturnedOnSearch.push(movie); // Add the movies that are a result to the search input value
});
};
const cacheTimestamp = localStorage.getItem("cacheTimestamp");
const expirationDuration = 21600000; // 6 hours in milliseconds
// Check if cache has expired or data is not available
if (
!cacheTimestamp ||
Date.now() - parseInt(cacheTimestamp) > expirationDuration
) {
// Cache expired or data not available, fetch movies again
fetchMovies();
} else {
// Use cached movie data
movieList = JSON.parse(localStorage.getItem("moviedata"));
renderMovies(movieList);
}
// Event listener and handler for search bar input
searchBar.addEventListener("input", (event) => {
searchValue = event.target.value.trim().toLowerCase();
// Filter movies based on search input
const filteredMovies = movieList.filter((movie) =>
movie.title.toLowerCase().includes(searchValue),
);
// Render the filtered movies on the page
renderMovies(filteredMovies);
if (moviesReturnedOnSearch.length <= 0) {
movieUnavailableTxt.style.display = "block"; // Show the "No movies found" message if no movies match the search
}
});
在本指南中,我們探討了使用API在JavaScript中實現實時搜索功能的方法。按照所述步驟,您可以創建一個動態搜索體驗,當用戶在搜索欄中輸入時,可以提供實時結果。
通過在您的網站上實現實時搜索功能,您可以增強用戶參與度,提高您的網站或應用程序的可用性。用戶將欣賞能夠快速方便地找到相關信息,而無需重新加載頁面。
通過這個指南所獲得的知識,您已經具備了在JavaScript中有效實現實時搜索功能的能力。擁抱動態搜索的力量,創造一個無縫的用戶體驗,給人留下深刻的印象。
由于文章內容篇幅有限,今天的內容就分享到這里,文章結尾,我想提醒您,文章的創作不易,如果您喜歡我的分享,請別忘了點贊和轉發,讓更多有需要的人看到。同時,如果您想獲取更多前端技術的知識,歡迎關注我,您的支持將是我分享最大的動力。我會持續輸出更多內容,敬請期待。
來了~歡迎轉發、收藏,記得點個贊唄。^_^
無論當前 JavaScript 代碼是內嵌還是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執行完成。JavaScript 執行過程耗時越久,瀏覽器等待響應用戶輸入的時間就越長。瀏覽器在下載和執行腳本時出現阻塞的原因在于,腳本可能會改變頁面或 JavaScript 的命名空間,它們對后面頁面內容造成影響。一個典型的例子就是在頁面中使用document.write()。例如清單 1。
清單 1 JavaScript 代碼內嵌示例
當瀏覽器遇到<script>標簽時,當前 HTML 頁面無從獲知 JavaScript 是否會向<p> 標簽添加內容,或引入其他元素,或甚至移除該標簽。因此,這時瀏覽器會停止處理頁面,先執行 JavaScript代碼,然后再繼續解析和渲染頁面。同樣的情況也發生在使用 src 屬性加載 JavaScript的過程中,瀏覽器必須先花時間下載外鏈文件中的代碼,然后解析并執行它。在這個過程中,頁面渲染和用戶交互完全被阻塞了。
HTML 4 規范指出 <script> 標簽可以放在 HTML 文檔的<head>或<body>中,并允許出現多次。Web 開發人員一般習慣在 <head> 中加載外鏈的 JavaScript,接著用 <link> 標簽用來加載外鏈的 CSS 文件或者其他頁面信息。例如清單 2。
清單 2 低效率腳本位置示例
然而這種常規的做法卻隱藏著嚴重的性能問題。在清單 2 的示例中,當瀏覽器解析到 <script> 標簽(第 4 行)時,瀏覽器會停止解析其后的內容,而優先下載腳本文件,并執行其中的代碼,這意味著,其后的 styles.css 樣式文件和<body>標簽都無法被加載,由于<body>標簽無法被加載,那么頁面自然就無法渲染了。因此在該 JavaScript 代碼完全執行完之前,頁面都是一片空白。圖 1 描述了頁面加載過程中腳本和樣式文件的下載過程。
圖 1 JavaScript 文件的加載和執行阻塞其他文件的下載
我們可以發現一個有趣的現象:第一個 JavaScript 文件開始下載,與此同時阻塞了頁面其他文件的下載。此外,從 script1.js 下載完成到 script2.js 開始下載前存在一個延時,這段時間正好是 script1.js 文件的執行過程。每個文件必須等到前一個文件下載并執行完成才會開始下載。在這些文件逐個下載過程中,用戶看到的是一片空白的頁面。
從 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 開始都允許并行下載 JavaScript 文件。這是個好消息,因為<script>標簽在下載外部資源時不會阻塞其他<script>標簽。遺憾的是,JavaScript 下載過程仍然會阻塞其他資源的下載,比如樣式文件和圖片。盡管腳本的下載過程不會互相影響,但頁面仍然必須等待所有 JavaScript 代碼下載并執行完成才能繼續。因此,盡管最新的瀏覽器通過允許并行下載提高了性能,但問題尚未完全解決,腳本阻塞仍然是一個問題。
由于腳本會阻塞頁面其他資源的下載,因此推薦將所有<script>標簽盡可能放到<body>標簽的底部,以盡量減少對整個頁面下載的影響。例如清單 3
清單 3 推薦的代碼放置位置示例
這段代碼展示了在 HTML 文檔中放置<script>標簽的推薦位置。盡管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完成并顯示給了用戶,因此頁面下載不會顯得太慢。
這是優化 JavaScript 的首要規則:將腳本放在底部。
由于每個<script>標簽初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>標簽數量有助于改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標簽,都會因執行腳本而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。
這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數量將會改善性能。
通常一個大型網站或應用需要依賴數個 JavaScript 文件。您可以把多個文件合并成一個,這樣只需要引用一個<script>標簽,就可以減少性能消耗。文件合并的工作可通過離線的打包工具或者一些實時的在線服務來實現。
需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的<link>之后會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息。因此,建議不要把內嵌腳本緊跟在<link>標簽后面。
減少 JavaScript 文件大小并限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上并不總是可行。Web 應用的功能越豐富,所需要的 JavaScript 代碼就越多,盡管下載單個較大的 JavaScript 文件只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要通過一些特定的技術向頁面中逐步加載 JavaScript 文件,這樣做在某種程度上來說不會阻塞瀏覽器。
無阻塞腳本的秘訣在于,在頁面加載完成后才加載 JavaScript 代碼。這就意味著在 window 對象的 onload事件觸發后再下載腳本。有多種方式可以實現這一效果。
HTML 4 為<script>標簽定義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標簽會以默認的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支持的話,這仍然是個有用的解決方案。清單 4 是一個例子
清單 4 defer 屬性使用方法示例
<script type="text/javascript" src="script1.js" defer></script>
帶有 defer 屬性的<script>標簽可以放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>標簽時開始下載,但不會執行,直到 DOM 加載完成,即onload事件觸發前才會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽器的其他進程,因此這類文件可以與其他資源文件一起并行下載。
任何帶有 defer 屬性的<script>元素在 DOM 完成加載之前都不會被執行,無論內嵌或者是外鏈腳本都是如此。清單 5 的例子展示了defer屬性如何影響腳本行為:
清單 5 defer 屬性對腳本行為的影響
這段代碼在頁面處理過程中彈出三次對話框。不支持 defer 屬性的瀏覽器的彈出順序是:"defer"、"script"、"load"。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:"script"、"defer"、"load"。請注意,帶有 defer 屬性的<script>元素不是跟在第二個后面執行,而是在 onload 事件被觸發前被調用。
如果您的目標瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那么 defer 腳本確實有用。如果您需要支持跨領域的多種瀏覽器,那么還有更一致的實現方式。
HTML 5 為<script>標簽定義了一個新的擴展屬性:async。它的作用和 defer 一樣,能夠異步地加載和執行腳本,不因為加載腳本而阻塞頁面的加載。但是有一點需要注意,在有 async 的情況下,JavaScript 腳本一旦下載好了就會執行,所以很有可能不是按照原本的順序來執行的。如果 JavaScript 腳本前后有依賴性,使用 async 就很有可能出現錯誤。
文檔對象模型(DOM)允許您使用 JavaScript 動態創建 HTML 的幾乎全部文檔內容。<script>元素與頁面其他元素一樣,可以非常容易地通過標準 DOM 函數創建:
清單 6 通過標準 DOM 函數創建<script>元素
新的<script>元素加載 script1.js 源文件。此文件當元素添加到頁面之后立刻開始下載。此技術的重點在于:無論在何處啟動下載,文件的下載和運行都不會阻塞其他頁面處理過程。您甚至可以將這些代碼放在<head>部分而不會對其余部分的頁面代碼造成影響(除了用于下載文件的 HTTP 連接)。
當文件使用動態腳本節點下載時,返回的代碼通常立即執行(除了 Firefox 和 Opera,他們將等待此前的所有動態腳本節點執行完畢)。當腳本是"自運行"類型時,這一機制運行正常,但是如果腳本只包含供頁面其他腳本調用調用的接口,則會帶來問題。這種情況下,您需要跟蹤腳本下載完成并是否準備妥善。可以使用動態 <script> 節點發出事件得到相關信息。
Firefox、Opera, Chorme 和 Safari 3+會在<script>節點接收完成之后發出一個 onload 事件。您可以監聽這一事件,以得到腳本準備好的通知:
清單 7 通過監聽 onload 事件加載 JavaScript 腳本
Internet Explorer 支持另一種實現方式,它發出一個 readystatechange 事件。<script>元素有一個 readyState 屬性,它的值隨著下載外部文件的過程而改變。readyState 有五種取值:
· "uninitialized":默認狀態
· "loading":下載開始
· "loaded":下載完成
· "interactive":下載完成但尚不可用
· "complete":所有數據已經準備好
微軟文檔上說,在<script>元素的生命周期中,readyState 的這些取值不一定全部出現,但并沒有指出哪些取值總會被用到。實踐中,我們最感興趣的是"loaded"和"complete"狀態。Internet Explorer 對這兩個 readyState 值所表示的最終狀態并不一致,有時<script>元素會得到"loader"卻從不出現"complete",但另外一些情況下出現"complete"而用不到"loaded"。最安全的辦法就是在 readystatechange 事件中檢查這兩種狀態,并且當其中一種狀態出現時,刪除 readystatechange 事件句柄(保證事件不會被處理兩次):
清單 8 通過檢查 readyState 狀態加載 JavaScript 腳本
大多數情況下,您希望調用一個函數就可以實現 JavaScript 文件的動態加載。下面的函數封裝了標準實現和 IE 實現所需的功能:
清單 9 通過函數進行封裝
此函數接收兩個參數:JavaScript 文件的 URL,和一個當 JavaScript 接收完成時觸發的回調函數。屬性檢查用于決定監視哪種事件。最后一步,設置 src 屬性,并將<script>元素添加至頁面。此 loadScript() 函數使用方法如下:
清單 10 loadScript()函數使用方法
您可以在頁面中動態加載很多 JavaScript 文件,但要注意,瀏覽器不保證文件加載的順序。所有主流瀏覽器之中,只有 Firefox 和 Opera 保證腳本按照您指定的順序執行。其他瀏覽器將按照服務器返回它們的次序下載并運行不同的代碼文件。您可以將下載操作串聯在一起以保證他們的次序,如下:
清單 11 通過 loadScript()函數加載多個 JavaScript 腳本
此代碼等待 script1.js 可用之后才開始加載 script2.js,等 script2.js 可用之后才開始加載 script3.js。雖然此方法可行,但如果要下載和執行的文件很多,還是有些麻煩。如果多個文件的次序十分重要,更好的辦法是將這些文件按照正確的次序連接成一個文件。獨立文件可以一次性下載所有代碼(由于這是異步進行的,使用一個大文件并沒有什么損失)。
動態腳本加載是非阻塞 JavaScript 下載中最常用的模式,因為它可以跨瀏覽器,而且簡單易用。
此技術首先創建一個 XHR 對象,然后下載 JavaScript 文件,接著用一個動態 <script> 元素將 JavaScript 代碼注入頁面。清單 12 是一個簡單的例子:
清單 12 通過 XHR 對象加載 JavaScript 腳本
此代碼向服務器發送一個獲取 script1.js 文件的 GET 請求。onreadystatechange 事件處理函數檢查 readyState 是不是 4,然后檢查 HTTP 狀態碼是不是有效(2XX 表示有效的回應,304 表示一個緩存響應)。如果收到了一個有效的響應,那么就創建一個新的<script>元素,將它的文本屬性設置為從服務器接收到的 responseText 字符串。這樣做實際上會創建一個帶有內聯代碼的<script>元素。一旦新<script>元素被添加到文檔,代碼將被執行,并準備使用。
這種方法的主要優點是,您可以下載不立即執行的 JavaScript 代碼。由于代碼返回在<script>標簽之外(換句話說不受<script>標簽約束),它下載后不會自動執行,這使得您可以推遲執行,直到一切都準備好了。另一個優點是,同樣的代碼在所有現代瀏覽器中都不會引發異常。
此方法最主要的限制是:JavaScript 文件必須與頁面放置在同一個域內,不能從 CDN 下載(CDN 指"內容投遞網絡(Content Delivery Network)",所以大型網頁通常不采用 XHR 腳本注入技術。
一般而言,減少 JavaScript 對性能的影響有以下幾種方法:
· A.將所有的<script>標簽放到頁面底部,也就是</body>閉合標簽之前,這能確保在腳本執行前頁面已經完成了渲染。
· B.盡可能地合并腳本。頁面中的<script>標簽越少,加載也就越快,響應也越迅速。無論是外鏈腳本還是內嵌腳本都是如此。
· C.采用無阻塞下載 JavaScript 腳本的方法:
· a-使用<script>標簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版本);
· b-使用動態創建的<script>元素來下載并執行代碼;
· c-使用 XHR 對象下載 JavaScript 代碼并注入頁面中。
通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網站和應用的實際性能。
看完了,別忘了收藏、轉發、點個贊,更別忘了要關注本號!^_^
SS加載確實有可能阻塞頁面加載,但這并非絕對,具體取決于CSS的加載方式、應用位置以及瀏覽器的渲染機制。在了解CSS加載如何影響頁面加載之前,我們先要明白瀏覽器渲染頁面的基本流程。
瀏覽器在加載網頁時,會按照從上到下的順序解析HTML文檔。當瀏覽器遇到`<link>`標簽引用外部CSS文件時,它會停止HTML的解析,轉而加載并應用這個CSS文件。這個過程被稱為CSS阻塞。因此,如果這個CSS文件很大或者加載速度很慢,用戶可能會看到一個空白頁面,直到CSS文件完全加載并應用。
然而,有幾種方法可以避免或減輕CSS加載對頁面加載的阻塞:
此外,值得注意的是,現代瀏覽器通常具有一些優化機制,如并行下載、緩存等,這些都可以幫助減少CSS加載對頁面加載的影響。
總的來說,CSS加載確實有可能阻塞頁面加載,但通過一些優化策略和技術,我們可以減輕或避免這種阻塞。選擇哪種策略取決于你的具體需求和約束。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。