Johnson Mao

Day.25 「從 事件綁定 與 定時器 認識回調函式!」 —— JavaScript 定時器 & Callback

「從 事件綁定 與 定時器 認識回調函式!」 —— JavaScript 定時器 & Callback

我們前面已經瞭解了事件綁定與事件冒泡了,但是使用 物件元素.綁定事件 有不方便的地方

  • 只能同時為一個元素的一個事件綁定一個響應函式
  • 不能綁定多個,如果綁定多個,後面會覆蓋前面的
複製成功!
btn.onclick = function () {
  console.log("我是第一個綁定事件");
}

btn.onclick = function () {
  console.log("我是第二個綁定事件");  // 這個綁定事件會覆蓋前面的綁定事件
}

/*
 "我是第二個綁定事件"
*/

#addEventListener()

這時可以使用最廣泛使用的事件監聽 事件監聽的參數

  • 第一個參數:要觸發的事件的字串,注意不要添加 on,例如:要綁定 onclick 事件,參數就寫 "click"
  • 第二個參數:觸發事件時的回調函式
  • 第三個參數:是否再捕獲階段觸發,布林值,通常情況下都是 false
複製成功!
btn.addEventListener("click", function(){
  console.log("我是第一個綁定事件");
}, false);

btn.addEventListener("click", function(){
  console.log("我是第二個綁定事件");    // 這樣就會直接"添加"綁定事件,而不會覆蓋
}, false);

/*
 "我是第一個綁定事件"
 "我是第二個綁定事件"
*/

與普通的事件綁定不一樣,事件監聽是用添加事件來綁定的,所以就不會覆蓋前面綁定的事件

#定時器

而有時候我們並不想透過事件監聽來觸發事件,而是設定時間,在開啟網頁後,一段時間觸發函式。 這時就要利用 JavaScript 定時器,就可以達到效果。

複製成功!
setTimeout(function(){
  console.log("時間到!")
}, 2000)

而除了 setTimeout 時間到只觸發一次的定時器,還有持續一段時間的 setInterval 定時器可以用。

#回調函式(Callback Function)

而我們在綁定事件定時器所使用的匿名函式就是所謂的回調函式

複製成功!
btn.addEventListener("click", function(){
  console.log("我是 DOM 事件的回調函式");
}, false);

setTimeout(function(){
  console.log("我是定時器的回調函式")
}, 2000)

#回調函式的特點

  • 我們自己設置的,這好像廢話
  • 我們沒有主動調用,讓函式變成另一個函式的參數
  • 但它自己會自動調用,讓函式控制參數函式的執行時機

所以上面看到的 addEventListener()setTimeout() ,顯而易見的都是函式! 而我們自己定義的函式就做為參數,等時機到了執行。

#常見的回調函式?

常見的回調函式有四個

  • DOM 事件回調函式
  • 定時器回調函式
  • AJAX 請求回調函式
  • 生命週期回調函式

#用函式控制執行函式的時機

首先我們先用定時器,計 0 秒馬上 console

複製成功!
function A () {
  setTimeout(function(){
    console.log("我是函式 A");
  }, 0)
}

function B () {
  console.log("我是函式 B");
}
function C () {
  console.log("我是函式 C,最後一個函式");
}

A();
B();
C();

/*
  "我是函式 B"
  "我是函式 C,最後一個函式"
  "我是函式 A"
*/

結果很神奇,沒想到 0 秒馬上 console 的函式,竟然最後才出現! 那是因為 JavaScript 在遇到定時器的時候,會先跳過這段函式,先繼續執行後面的程式碼,在執行定時器(此時已經有毫秒的延遲),這樣的好處當然就是不用苦苦地等計時器結束,讓可以先運作的運作完,讓使用者不會有等待的感覺,而這個現象稱作非同步

而後面的函式又與計時器的這個函式有關的話!就會使用 Callback Function 來處理~

複製成功!
function A (FnB, FnC) {
  setTimeout(function(){
    console.log("我是函式 A");
    FnB( FnC );
  }, 0)
}

function B (Fn) {
  console.log("我是函式 B");
  Fn();
}
function C () {
  console.log("我是函式 C,最後一個函式");
}

A(B, C);

/*
  "我是函式 A"
  "我是函式 B"
  "我是函式 C,最後一個函式"
*/

這時在確定執行完 A 後,才會接續執行 BC

#回調函式的優缺點

#優點

  • 能夠確保執行的時機
  • 更好維護

例如要添加 D 函式就可以直接使用參數帶入

複製成功!
function A (FnB, FnC) {
  setTimeout(function(){
    console.log("我是函式 A");
    FnB( FnC );
  }, 0)
}

function B (Fn) {
  console.log("我是函式 B");
  Fn();
}
function C () {
  console.log("我是函式 C,最後一個函式");
}
function D (Fn) {
  console.log("我是函式 D")
  Fn;
}

D( A(B, C));

/*
  "我是函式 D"
  "我是函式 A"
  "我是函式 B"
  "我是函式 C,最後一個函式"
*/

#缺點

相信學程式到一定程度的人,多少都有聽過宛如波動拳的 Callback Hell 吧!

複製成功!
function (n, fnA, fnB, fnC, fnD, fnE) {
    if (fnA(n)) {
        if (fnB(n)) {
            if (fnC(n)) {
                if (fnD(n)) {
                    if (fnE(n)) {
                      console.log("fnE");
                    } else {
                      console.log("fnD");
                    }
                } else {
                  console.log("fnC");
                }
            } else {
              console.log("fnB");
            }
        } else {
          console.log("fnA");
        }
    } else {
      console.log("null");
    }
}

#總結

回調函式(Callback Function)是個很常見的寫法,但要小心使用,使用不當後續會很痛苦,而後面又有推出 PromiseAsync / Await ,就解決了同步與非同步的問題!

#參考資料

回首頁