iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 18
1
自我挑戰組

WordPress 客製化從 0 開始系列 第 18

Day 18 用 WordPress 看 JavaScript 非同步處理與 AJAX

大家好,我是 Eric。
今天要來介紹 JavaScript 比較進階的運用方式:非同步 (asynchronous) 處理。
JavaScript 是一種直譯式語言,意思是他會逐行檢視程式碼,「看到什麼執行甚麼」。但也因為這個特性,因此 JavaScript 的靜態資源 (.js 檔案) 便會在瀏覽器轉譯 (render) 頁面時,被 JavaScript 阻擋,等到全部的程式碼執行後,才能繼續往下轉譯剩下的內容。
如果你有使用 Google PageSpeed Insights 或 Lighthouse,這種狀況就是所謂的禁止轉譯資源 (render-blocking resources)。對於重視分數的人來說,這會是一項顯著的扣分項目。

async 與 defer

在 PageSpeed Insights 中,如果你的靜態資源被歸類為禁止轉譯資源,Google 會建議你為靜態資源的語法加上 asyncdefer 的屬性。
透過這個方法,你可以讓瀏覽器在讀到這個元素時,可以先繼續執行後續的 HTML 語法,並同時在背景下載這些靜態資源檔案。
我們以在國稅局為例 (因為這幾天剛好去國稅局,所以拿這個當案例),在我們到窗口時,發現要填寫各種表格。這時候:

同步處理的作法

  • 向窗口提出請求 (request)
  • 窗口等待你臨櫃把表單全部填寫完畢,接著處理剩下步驟 (processing)
  • 等到你的案件處理完畢後,將單據或憑證提供給你 (response)
  • 再開始叫號,輪到下一位民眾

這種作法,蠻沒效率的。有時候會淪為「全世界都等你一個」的情況。我們接著看看不同的作法:

非同步處理的作法

  • 向窗口提出請求 (request)
  • 窗口發現你的表單填寫不全,請你先到旁邊填表,輪到下一位民眾
  • 等你填完表單後,再將資料提供給窗口,接著處理剩下步驟 (processing)
  • 窗口提供單據或憑證 (response)

這麼做,是不是讓排隊的流程更有效率了?JavaScript 的非同步處理也是一樣的道理。當 HTML 轉譯到靜態資源的元素時,會先告訴瀏覽器「你先繼續後面的工作,我等等下載完之後再告訴你。」藉此縮短後續轉譯的時間。而這當中,又分為 async 的非同步,或是 defer 的非同步,其差異如下:

屬性 | 說明 | 白話說明 (國稅局案例)
-------+-------+-------
async | async 的作法,並不會按照元素在 HTML 中的排序,而是優先載入已經下載完畢的資源。 | 窗口請你到一旁填表單,誰先填完,就會先中止手邊的案件,繼續處理你的案件。
defer | defer 的作法,則會在全部的 DOM 的元素都解析完以後,才會開始執行後續的資源。 | 窗口請你到一旁填表單,填完以後,要等到目前排隊民眾的案件都處理完後,才會繼續處理你的案件。

在 WordPress 中實作 asyncdefer

我們可以先看一下 asyncdefer 在 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% 都有外掛可以幫你解決。以 asyncdefer 的非同步處理來說,快取外掛 Autoptimize 的開發商,同時提供了一個名為 Async JavaScript 的外掛,可以直接替網站中的 JavaScript 加上 asyncdefer 的屬性。
除了 Async JavaScript 以外,坊間常見的快取外掛,幾乎都會搭配這樣的功能,例如 W3 Total CacheWP Fastest Cache 等。

AJAX

我們在介紹 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 條目。這邊接著說明 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 中使用 AJAX

在 WordPress 客製化的過程中,我們必須先從 PHP 著手,定義好各種功能後,再由 JavaScript 的 AJAX 來取得回傳的結果。在 WordPress 開發 AJAX 功能的作法,可以參考社群夥伴阿竣的這篇文章。因為涉及 PHP 的開發流程,我們在後面才會再有更多的著墨。

與 AJAX 有關的外掛

在 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 的編碼標準。


上一篇
Day 17 用 WordPress 製作投影片輪播 (slider) 效果
下一篇
Day 19 WordPress 對 JavaScript 的編碼標準
系列文
WordPress 客製化從 0 開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
yclin925
iT邦新手 5 級 ‧ 2022-06-26 09:28:22

謝謝大大分享,想問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 );
}

我要留言

立即登入留言