大家好,我是 Eric。
今天要來介紹 JavaScript 比較進階的運用方式:非同步 (asynchronous) 處理。
JavaScript 是一種直譯式語言,意思是他會逐行檢視程式碼,「看到什麼執行甚麼」。但也因為這個特性,因此 JavaScript 的靜態資源 (.js 檔案) 便會在瀏覽器轉譯 (render) 頁面時,被 JavaScript 阻擋,等到全部的程式碼執行後,才能繼續往下轉譯剩下的內容。
如果你有使用 Google PageSpeed Insights 或 Lighthouse,這種狀況就是所謂的禁止轉譯資源 (render-blocking resources)。對於重視分數的人來說,這會是一項顯著的扣分項目。
在 PageSpeed Insights 中,如果你的靜態資源被歸類為禁止轉譯資源,Google 會建議你為靜態資源的語法加上 async
或 defer
的屬性。
透過這個方法,你可以讓瀏覽器在讀到這個元素時,可以先繼續執行後續的 HTML 語法,並同時在背景下載這些靜態資源檔案。
我們以在國稅局為例 (因為這幾天剛好去國稅局,所以拿這個當案例),在我們到窗口時,發現要填寫各種表格。這時候:
這種作法,蠻沒效率的。有時候會淪為「全世界都等你一個」的情況。我們接著看看不同的作法:
這麼做,是不是讓排隊的流程更有效率了?JavaScript 的非同步處理也是一樣的道理。當 HTML 轉譯到靜態資源的元素時,會先告訴瀏覽器「你先繼續後面的工作,我等等下載完之後再告訴你。」藉此縮短後續轉譯的時間。而這當中,又分為 async
的非同步,或是 defer
的非同步,其差異如下:
屬性 | 說明 | 白話說明 (國稅局案例)
-------+-------+-------
async | async
的作法,並不會按照元素在 HTML 中的排序,而是優先載入已經下載完畢的資源。 | 窗口請你到一旁填表單,誰先填完,就會先中止手邊的案件,繼續處理你的案件。
defer | defer
的作法,則會在全部的 DOM 的元素都解析完以後,才會開始執行後續的資源。 | 窗口請你到一旁填表單,填完以後,要等到目前排隊民眾的案件都處理完後,才會繼續處理你的案件。
async
與 defer
我們可以先看一下 async
與 defer
在 HTML 中的實作語法:
<script async src="my-async-js.js"></script>
<script defer src="my-defer-js.js"></script>
雖然我們還沒有進入 PHP 的主題,但這裡先提供 G.T. Wang 的解決方案。和其他客製化一樣,你可以透過修改子佈景主題的 functions.php 或使用外掛 Code Snippets 來達成。
/**
* 參考資料 G.T. Wang https://blog.gtwang.org/web-development/using-defer-or-async-with-scripts-in-wordpress/
*/
/* 把指定的 JavaScript 檔加入 async 或 defer 屬性 */
function gtwang_defer_js_async($tag){
// 要加入 defer 屬性的 JavaScript 檔
$scripts_to_defer = array(
'script-name1.js',
'script-name2.js',
'script-name3.js');
// 要加入 async 屬性的 JavaScript 檔
$scripts_to_async = array(
'script-name4.js',
'script-name5.js',
'script-name6.js');
// 處理 defer
foreach($scripts_to_defer as $defer_script){
if(true == strpos($tag, $defer_script ) )
return str_replace( ' src', ' defer="defer" src', $tag );
}
// 處理 async
foreach($scripts_to_async as $async_script){
if(true == strpos($tag, $async_script ) )
return str_replace( ' src', ' async="async" src', $tag );
}
return $tag;
}
add_filter( 'script_loader_tag', 'gtwang_defer_js_async', 10 );
就如同我們之前一再提到的:你想得到的功能,80% 都有外掛可以幫你解決。以 async
與 defer
的非同步處理來說,快取外掛 Autoptimize 的開發商,同時提供了一個名為 Async JavaScript 的外掛,可以直接替網站中的 JavaScript 加上 async
與 defer
的屬性。
除了 Async JavaScript 以外,坊間常見的快取外掛,幾乎都會搭配這樣的功能,例如 W3 Total Cache、WP Fastest Cache 等。
我們在介紹 DOM 與 jQuery 的時候,曾經提到 AJAX 這個詞,它便是 Asynchronous JavaScript And XML 的縮寫,意思是非同化的 JavaScript 與 XML 處理。
大約在 15 年前,AJAX 還尚未普及於網站開發中。當時網站上的表單元素,在提交時,都會有一個 action
的標的。即使是要停留在原本的頁面,也會是透過 action="當前頁面.php"
的作法,因此在提交表單後,通常都會有個重新整理頁面的過程。
但是隨著 Web App 開始普及,以及當代將前後端分離的設計模式,AJAX 這種根據特定事件 (通常就是點擊) 透過 JavaScript 向後端存取資料的方式,開始廣泛的運用在各個網站中。
讓我們回到國稅局的例子。
過去的作法,窗口在取得你的請求後,會走到 3 樓的資料間,用裡面的電腦查詢你的稅籍資料後,在那台電腦處理完你的案件,接著印出單據或憑證,接著拿回 1 樓把資料給你。如果今天有 10 個人同時在做這件事,代表會有 10 個窗口同時要上下走 1 - 3 樓的樓梯。
但透過 AJAX,每個窗口都可以在 1 樓櫃檯,將你的資料透過內網,向 3 樓的資料間查詢稅籍資料。接著用 1 樓的印表機將你的單據或憑證印出來。
上面的故事,我們對於 AJAX 的實作過程稍作簡化,但概念上就是這個流程。
WordPress 處理 AJAX 的情境,最常見的是自動儲存。大家都有過經驗,在編輯頁面時,先把寫好的草稿儲存起來後,等到檢查得差不多,才會再發佈。你在儲存的過程中,頁面不會重新整理,而是直接透過 AJAX 的方法幫你儲存進資料庫中。
關於 AJAX 的詳細說明,可以參考維基百科的 AJAX 條目。這邊接著說明 AJAX 的基本語法,主要參照程式前沿的作法:
// 為了符合 WordPress 的情境,所以改匿名函式的作法。
(function($){
$.ajax({
url: "{{請求的對象網址/你的窗口}}",
dataType: "json", //回傳格式,通常會是 JSON
data: { "id": "value" }, //引數,通常會需要參考對方的 API 文件
type: "GET", //請求方式,可以理解為提交表單的方法。常見的主要有 GET/POST
beforeSend: function() {
//選用,通常會是要處理請求標頭 (request header)
},
success: function(req) {
//請求成功時做的處理,最主要的回呼函式會在這裡進行
},
complete: function() {
//請求完成後的處理
},
error: function() {
//請求出錯處理,主要是拿來處理錯誤訊息
}
});
})(jQuery);
通常透過 AJAX 方法所取得的資料,會以 JSON 的形式回傳。有關 JSON 的介紹,可以參閱維基百科的相關條目。我們在取得 JSON 物件後,會再自行製作相對應的 DOM 元素,嵌入回當前的頁面中。
這邊嘗試透過 AJAX 的方式取得政府的開放資料。可以把下列語法複製到瀏覽器的模擬器面板,就可以取得〈船級作業風險-依船噸位分級作業風險預報4天〉的資料。詳細的執行結果可以參考這個 CodePen。接著我們就可以再透過 jQuery 製作想呈現的前端效果。
(function($){
$.ajax({
url: "https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-B0082-001",
dataType: "json", //回傳格式,通常會是 JSON
data: {
Authorization: "rdec-key-123-45678-011121314",
format: "json"},
type: "GET",
success: function(req) {
console.log();
$("#result").append(req.cwbdata.note); //將資料回嵌至 #result 之下
},
error: function(e){
console.log(e.responseText.note);
}
});
})(jQuery);
在 WordPress 客製化的過程中,我們必須先從 PHP 著手,定義好各種功能後,再由 JavaScript 的 AJAX 來取得回傳的結果。在 WordPress 開發 AJAX 功能的作法,可以參考社群夥伴阿竣的這篇文章。因為涉及 PHP 的開發流程,我們在後面才會再有更多的著墨。
在 WordPress 中,有許多可以協助站長處理 AJAX 的外掛,其中 Post Grid with Ajax Filter 這款外掛,可以滿足站長想要動態篩選顯示文章的功能。
WordPress Infinite Scroll – Ajax Load More 這款外掛,則是可以協助站長製作無限捲動 (infinite scroll) 的效果。相較於 Jetpack 內建的無限捲動,這款外掛提供較多可調整的彈性。
Ivory Search 則是一款支援動態呈現搜尋結果的進階搜尋外掛。
Contact Form 7 這是一款可以稱得上是 WordPress 標配的表單外掛,除了可供客製化的彈性外,開發文件齊全,也自己發展出了一套自己的生態系。它基本的使用方式,就是透過 AJAX 來提交表單。
今天介紹的 JavaScript 的非同步處理,算是在 JavaScript 的學習中,較為進階的議題。這是因為 AJAX 除了前端 JavaScript 外,還需要有與網站後端溝通的設計環節在。因此除非能夠好好讀懂其他人技術文件中的說明,不然很難寫好一份自己的 AJAX 程式。
這次提供 WordPress 外掛的目的,在於告訴大家 AJAX 可以實際運用在什麼樣的情境中,希望透過簡單的介紹,讓大家知道怎麼樣利用 jQuery 送出、接回 AJAX 的請求。
明天,我們將會進入 JavaScript 的終章,一樣要來介紹 WordPress 針對 JavaScript 的編碼標準。
謝謝大大分享,想問async/defer範例程式碼是否太早return?謝謝
// 處理 defer
foreach($scripts_to_defer as $defer_script){
if(true == strpos($tag, $defer_script ) )
return str_replace( ' src', ' defer="defer" src', $tag );
// suggest:$tag = str_replace( ' src', ' defer="defer" src', $tag );
}
// 處理 async
foreach($scripts_to_async as $async_script){
if(true == strpos($tag, $async_script ) )
return str_replace( ' src', ' async="async" src', $tag );
// suggest:$tag = str_replace( ' src', ' async="async" src', $tag );
}