Web Development 網站開發 (8) - Javascript 非同步:Function and Callback
Callback 是 Javascript 的一個特色。簡單來說就是把一個 function 當作另一個 function 的參數傳進去。其他的程式語言也有類似的方法,但在 Javascript 裡應該是使用最多的。我想原因應該是來自於其使用的場景,Javascript 原先就是用來開發網頁的,需要監聽來自於使用者的各種操作並作出即時的響應,非同步搭配 Callback 的設計能很好的執行任務。
Callback 是 Javascript 的一個特色。簡單來說就是把一個 function 當作另一個 function 的參數傳進去。其他的程式語言也有類似的方法,但在 Javascript 裡應該是使用最多的。我想原因應該是來自於其使用的場景,Javascript 原先就是用來開發網頁的,需要監聽來自於使用者的各種操作並作出即時的響應,非同步搭配 Callback 的設計能很好的執行任務。
Function
在 Javascript 裡的 function 有各式各樣的寫法。有基本的、匿名的還有 arrow function 等,可以直接看一下例子:
// 最基本的 function
function add(a, b) {
return a + b;
}
add(1, 2)
// javascript 裡的 function 是 object 的一種,可以被 assign 給變數
// 因為這個 function 立刻就會被 assign 給 add1,那我們不用定義 function name。
// 當然因為他被 assign 給 add1,所以你終究還是會得到一個 add 的 function
const add1 = function(a, b) {
return a + b;
}
add1(2, 3)
// 在 object 底下定義 object 有以下方法:
const obj = {
add(a, b) {
return a + b;
}
}
obj.add(1, 2);
// assign 給 obj1 裡的 add 成員,跟上面一般的 assign 一樣也可以是匿名
const obj1 = {
add: function(a, b) {
return a + b;
}
}
obj1.add(1, 2);
// 這個是另一種 function 的寫法,我們稱之為 arrow function,除了語法上的不同外大部分的地方都是一樣的,有一個差別是 `this` 在 arrow function 裡的意義跟一般的 functino 不太一樣,這邊先不提。
const add2 = (a, b) => {
return a + b;
}
add2(1, 2);
// arrow function 的 inline 格式,變得很簡潔
const add3 = (a, b) => a + b;
add3(2, 3);
// arrow function 也可以 assign 在 object 裡
const obj2 = {
add: (a, b) => a + b
}
obj2.add(1, 2);
以上我們知道了 function 是一個 object 所以可以被 assign 進一個變數裡。那既然變數可以被當作參數傳進 function 裡,代表說我們可以把一個 function 當成參數傳進另一個 function 裡!
function a() {
console.log('I am a');
}
function b(func) {
func();
console.log('I am b');
}
// a 這個 function 被當作參數傳給 b,也就是第五行的 `func`
// 第六行 call 了 func(),也就是 call 了 a,這時就會印出 I am a
// 接著執行第七行,印出 I am b
// 所以執行這個 functino 會先印出 I am a 然後 I am b
b(a);
Callback
以上 function call function 就是 Callback 的核心概念,如果要問為什麼 Callback 在 Javascript 的世界裡這麼重要的話,大概就是因為 Javascript 是一個 single threaded 的語言。
大部分的語言要同時執行多件事都是開 multi threads,讓不同的任務在不同的 threads 裡執行。Javascript 只有一個 thread ,如果要在相近的時間內執行多個任務,就必須要用 callback 的方式。這邊做一個完後 callback 回去做另一個,再搭配 event loop 來達成這種 “非同步” 的行為。 (event loop的話題也很有趣,可能需要另外一個篇幅來解釋)
// setInterval 是瀏覽器內建給你的 function,使用方式是傳進一個 function 給他
// 並指定秒數(1000 = 1 sec),他會每隔一段你指定的時間執行你定義的 function
// 這個 case 他會每格一秒把 counter 加一然後印出來
// 其實他在做的事情就是你先call它,然後他每一秒callback回來。你的程式不會被 setTimeout 卡住,它會繼續往下執行(非同步)。
let counter = 0;
setInterval(function () {
console.log(++counter);
}, 1000);
// Callback 不是一定只能用在非同步的場景,很多內建的函式要求你傳callback進去來完成任務,更像是挖格子填內容的概念。
// Case1: forEach
// array 內建有 forEach,它會試著去 iterate 每一個 array 裡的元素,並且用當下的元素當作參數塞你定義的函數去執行。
const arr = [1, 2, 3];
arr.forEach(function(item) {
console.log(item); // 會依序列印 1, 2, 3
});
// Case2: map
// array 內建 map,它會試著去 iterate 每一個 array 裡的元素,並且用當下的元素當作參數塞你定義的函數去執行,你的 function 必須要回傳一個結果,它會搜集你回傳的每個結果,再組出一個新的 array。
// 除了基本的 function 以外,這些內建函數都允許你塞 arrow function
// 我們在 map 把傳進來的數 +1 再回傳,也就是說以下做的事情是把當下 array 的元素全部加1組成新的 array。
const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item + 1);
console.log(arr2); // arr2 現在是 [2, 3, 4]
可以看看一個有 forEach 功能的 function 是怎麼實現出來的:
// Bonus: 如何實現一個 forEach?
Array.prototype.myForEach = function(fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i]);
}
}
const arr = [1, 2, 3];
arr.myForEach(function(item) {
console.log(item); // 會依序列印 1, 2, 3
});
// 解釋:
// 用 prototype 可以幫內建的 array 型態新增一個字定義的成員
// Array.prototype.myForEach 定義好以後,你就可以對一個 array 互叫 myForEach
// myForEach 的參數是 function 所以我們在 line2 定義了 `fn`
// 用 arr.myForEach 時,myForEach 裡的 this 就是指 arr
// line 3-5 就是 iterate 這個 this(array),然後把 array 的每個元素依照順序當成參數傳到傳進來的 function fn裡執行。
最後面不得不提到最常用的事件監聽,用戶的操作可以在任何時間點發生,我以我們的事件監聽 Function 理所當然是非同步,需要給一個 Callback 去處理事件。
const button = document.getElementById('button1');
// 這裡的 addEventListenr 帶了兩個參數 (第三個後省略),第一個是監聽的事件,
// 第二個是一個匿名的 callback function。
// 當這個按鈕點擊時,callback function 會被呼叫,所以執行 line7 的視窗彈跳。
button.addEventListener('click', function() {
alert('button1 is clicked');
})
const input = document.getElementById('username_input');
// keypress 是鍵盤按下的事件偵測,假如今天有人在 username_input 這個 input
// 裡打字了那這裡的 callback function 就會被觸發
input.addEventListener('keypress', function(event) {
// do something
});
// 事件有非常多種,滑鼠滾動、滑鼠移動、圖片加載完成、頁面加載完成等各式各樣的事件
// 可以參考這裡 https://developer.mozilla.org/en-US/docs/Web/API/Event/type