這邊要特別說明callback function,它相當重要,尤其是應用在同步/非同步的時候。
當一個函式作為引數的時候,在運算過程會由另一個函式來呼叫它,說穿了,callback function跟一般的函式都一樣,只是呼叫的方式不同而已。
常使用的事件監聽或setTimeout( )函式,就是最典型的例子,當事件觸發或時間一到,就會呼叫相對應的函式作處理。
document.addEventListener('click', function () {
console.log('Mouse Click!');
});
setTimeout(function () {
console.log('Hello World');
}, 1000);
以上的callback function是匿名函式,也就是沒有函式名稱,因為整個函式定義都被包含在另一個函式的參數列中,所以無需命名。
callback function也可以定義在全域執行環境中。
document.addEventListener('click', onclick);
function onclick() {
console.log('Mouse Click!');
}
setTimeout(greeting, 1000);
function greeting() {
console.log('Hello World!');
}
另一種形式的callback function
function funcA(callback) {
callback();
}
function funcB() {
console.log('Hello World');
}
funcA(funcB); //Hello World
funcA的的本體中,呼叫callback( ),意味著如果我們傳入的是函式的話,那該函式將會被funcA呼叫,這邊放入其他型別的引數會引發錯誤,不過為了不讓各位混淆,就不寫判斷式了。
呼叫funcA,funcB被當作引數傳入,並執行,所以結果會印出Hello World。
以上的範例輸出的內容是寫死的,我們稍微修改一下。
function funcA(x,callback) {
callback(x);
}
function funcB(val) {
console.log(val);
}
funcA('Hello',funcB); //Hello
在funcA加入一個參數x,代表欲輸出的內容。callback不變,依舊是欲執行的函式。
再來,我們改用匿名函式。
function funcA(x, callback) {
callback(x);
}
funcA('Hello', (val) => {
console.log(val);
});
這邊使用箭頭函式,乍看之下,感覺有點複雜,再回去看看第一個callback function的例子,callback function整個函式定義都被包含在另一個函式的參數列中,比對上面的範例
(val) => {
console.log(val);
}
這個函式不就是被定義在funcA的參數欄中嗎?
為何需要用callback function這種複雜的方式?
callback function很大的好處是,我們可以自己定義函式執行的順序。
或許你會有疑問,我們大可在全域環境中,一行一行的執行函式,它就會按照順序執行了阿,話說對了一半,那是因為我們還沒講到同步/非同的概念,這部分之後會講,因為這是一個很大的議題,必須得分批來闡述,才比較容易理解。
每次講到callback function,有經驗的工程師,8成都會想到(Callback Hell)經典圖,有興趣的可以google。
回到剛剛的範例,funcA執行完後,接著會執行funcB,是吧,那如果我有ABCD這四個函式,必須得確保按照順序執行呢?
function funcA(a, callback) {
console.log('do funcA.....');
callback(a);
}
function funcB(b, callback) {
console.log('do funcB.....');
callback(b);
}
function funcC(c, callback) {
console.log('do funcC.....');
callback(c);
}
function funcD(d, callback) {
console.log('do funcD.....');
callback(d);
}
funcA('回', (val1) => {
console.log(val1);
funcB('呼', (val2) => {
console.log(val2);
funcC('地', (val3) => {
console.log(val3);
funcD('獄', (val4) => {
console.log(val4);
})
})
})
});
這樣的方式雖然可以確保函式會依ABCD順序執行,但可讀性跟維護性非常差,
來說明一下,它的執行順序。
我們一共執行8個函式,有看出來嗎?箭頭函式也算喔。
單純依函是順序來看,順序是,funcA>(val1)> funcB>(val2)>funcC>(val3)>funcD>(val4)。
不過好消息是,ES6多了一個Promise物件,讓我們遠離Callback Hell,之後的章節會說明。