前置觀念
1、何謂函數的參數?
function test1(x) {
console.log(x);
}
test1是我們定義的一個function,x就是它的一個參數,參數只是一個容器代號,當呼叫此函數時,我們可以傳遞一個常數、變數、或是函數,傳遞的內容會放在x裡,在函數內部可以透過讀取x取得放在裡面的內容,例如:
test1(3);
函數名稱後面加上'( )' 代表執行這個函數,在'( )'裡面置入的內容會存到x裡面,以此例而言就是3,在函數內部的x就等於3
因為函數內部只有一行程式碼console.log(x),x此時等於3,所以函數執行結果就是輸出3這個數字
2、何謂callback?
被當做參數傳遞的函數我們稱它為callback。
function test2(x){
x();
}
function test3( ){
console.log('123');
}
test2(test3);
上面我們定義兩個函數test2和test3,並在最後透過在函數名稱後面加上'( )'執行了test2這個函數,並把test3這個函數當做參數傳遞進去test2函數內部,所以此時test2函數的參數x就等於test3這個函數
而test2裡面只有一行程式碼 x( ); 因為x此時等同test3函數,因此此行程式碼等於 test3( ); 也就是執行test3這個函數的意思,所以結果就是輸出 123。
上面這個例子裡面test3這個function就是一個callback,callback只是一個對這種被當做參數傳入的函數的通稱
另外說明一下函數名稱是用來識別函數用的,函數名稱加上( )則是執行該函數,因此test2(test3)代表的是執行test2函數,並把test3這個函數傳遞進去,而test2( test3( ) )代表的是執行test2這個函數,並把test3的執行結果傳遞進去
因為test3( )的執行結果是輸出123,但沒有回傳值,因此test3( )的結果就是什麼都沒有,此時x裡面裝的就是空值,因此test2裡面的那行 x( ); 就會出錯,它會跟你講x不是一個function,因此無法執行,所以要記得傳遞的是函數本身,不可以加上( )哦
3、為什麼需要callback?
因為js語言的異步(asynchronous)特性,
需要將函數當做參數使用
那麼何謂異步(或稱nonblocking)?
console.log(1);
console.log(2);
console.log(3);
上面這3行程式碼執行的結果是輸出: 1 , 2 , 3,是一行接著一行執按照順序執行,一般程式碼都是這樣逐行執行的
但js語言中有一些方法不是這樣運作的,例如jquery的 $.get( )方法,這個方法的用途是向server發出get請求,server會依請求內容回覆對應的資料,這代表這個動作有一個和server通訊的過程,是需要時間的,請看以下程式碼
console.log(1);
$.get( );//這邊先不管get方法實際的運作方式
console.log(3);
若是逐行執行的模式,console.log(3); 會等$.get( );執行完(也就是和server通信完畢)才執行,但jquery的$.get( )方法則是會先讓程式往下執行,等到它和server通訊結束才會透過事件的方式觸發指定給它的callback函數
我們先假設$.get( );方法完成後會輸出'我完成了'內容,上面三行程式碼的結果會是輸出: 1 , 3 , '我完成了'
第二行的程式最後才顯示結果,沒有照順序執行,這就是異步的基本概念,但是並不是所有js的方法都是異步的,到這邊我們先瞭解js語言有部份方法是異步的即可。
那麼對於異步的方法(要達成某種目的的函數),我們要怎麼處理它?
通常,只要是異步的方法例如$.get( ) 在執行結束時都會觸發特定的事件,同時這個異步的方法接受一個自定義的函數作為參數,在對應的事件觸發時,執行這個函數,並把異步作業執行的結果以及狀態當做參數傳送給這個自定義的函數,以$.get( )而言就是server傳回的資料和連線狀態,
例如我們來看看$.get( )實際運作的語法格式
$.get(url,callback);
可以看到$.get( )有兩個參數,其中url是要通訊的server網址,callback是當和server通訊結束時要執行的函數(因為這個函數是$.get( )的一個參數,泛稱為callback,其實就是給他一個函數)
$.get( 'http://xxxxxxxxx' , function (data,status){
//data是server傳回的資料,status是連線的狀態,目前用不到
//$.get( )方法和server通訊結束後會執行這個function,
//並把server回傳的資料放到data參數裡,
//把連線狀態放到status參數裡,
//在這個function裡就可以拿它們來用或是做處理
//例如要辨別傳回資料內容的型態,函數裡可以寫如下程式
console.log(typeof data);
});
$.post( )也是一個異步的方法,
它接受三個參數,url,postData,callback
$.post(url,postData,callback),和get方法一樣,只是多了一個postData參數,此參數是要傳遞給server的資料內容
有發現callback函數都是最後一個參數嗎?通常異步方法的最後一個參數都是放callback函數,也就是在動作執行完後要執行的函數
所以異步方法的結構就像下面這樣:
某個異步方法(參數1,參數2,參數3,...., 參數n,作業完成要執行的callback函數)
異步方法在作業完成後執行指定給它的callback函數,並將取得的資料當做參數傳遞給它的callback函數,以做後續處理
例如向server要求書籍資料,取得後將資料丟給callback函數,在callback函數裡面對書籍資料做處理,例如將書籍資料顯示在頁面上等等
4、如何判斷哪些方法是異步的?
通常需要你指定callback函數的方法就是異步的
5、何謂匿名函數?
顧名思義就是沒有名稱的函數。
名稱的用途是識別與重複指派,在某些情況下並不需要識別與指派,這時為了方便起見,在宣告的同時就指派給特定目標
很常運用的情境就是指派給異步方法的函數,還有就是指派給物件方法的函數,匿名函數的好處是不用先宣告再指派,在宣告同時指派,方便使用
匿名函數運用1,指派callback:
$.get('http://......', function ( ){//寫想做的事});
不匿名也可以,但就是多一個先宣告的動作:
//宣告函數
function test6( ){
//寫想做的事
}
//指派test6給$.get( )方法
$.get('http://......', test6);
匿名函數運用2,指派函數給物件做為方法:
var myObj = {};
myObj.jump = function (){//寫想做的事};
匿名函數運用3,指派函數給特定變數:
var test7 = function ( ) {
//想做的事
}
其實上例類似下面這個方式
function test7 ( ){
//想做的事
}
差別只在指派給變數,需先宣告才能使用,直接宣告函數名稱則不用,以下舉例:
//以下這段執行會出錯
test7( );
var test7 = function ( ) {
//想做的事
}
//以下這段可正常運作
teset7( );
function test7 ( ){
//想做的事
}
這是js的另一個特性,變數宣告(含函數宣告)會自動拉升到最上層(原文叫hoist),但指派的值不會拉升
所以有名稱的函數可以先調用再宣告,指定給變數的函數則不行
6、萬事俱備,範例解析:
現在應該可以看得懂歐老師的範例了,已加入說明。
<script>
//先說明,以下程式碼是歐老師的寫法,不一定要這樣寫,重點是看得懂在做什麼即可
//下面這段宣告一個匿名函數指派給getBooks變數,讓getBooks代表這個函數
//前面用const或是var或是let在這裡都沒影響,其實直接宣告一個叫getBooks的函數也行
//總之就是在宣告一個有兩個參數的函數,其中一個參數用來接收頁數,
//一個用來接收函數(把函數當參數傳遞我們會稱呼它為為什麼?對!就是callback,
//所以這裡參數命名為callback,其實你要叫x或是y都可以,它就只是一個容器)
//這個函數的用途是取得書籍資料
const getBooks = function(page, callback) {
//依傳入的頁籤號碼(page的內容),和固定部份組合成完整的網址
const url = 'http://api.0x427567.com:8080/book?page=' + page
//執行$.get();方法,傳入組合好的網址並宣告一個匿名函數給它,
//這個匿名函數有一個參數叫books,是準備用來存放從server回傳的資料用的,
//$.get()方法在結束和server的通訊後,會呼叫這個函數,
//並把取得的資料丟到books這個參數裡
$.get(url, function(books) {
//執行呼叫getBooks這個函數時傳入的callback函數(也就是第二個參數),
//並把從server取得並已放在books參數裡的資料傳送給這個callback函數,
//等下下面真正執行getBooks這個函數時,
//就會看到傳進來的callback函數到底是哪支了以及內容是在做什麼了
callback(books)
})
}
//下面這段宣告一個用來將取得的書籍資料顯示在頁面上的函數,
//設定一個參數用來接收書籍資料
const generateList = function(books) {
//用for迴圈將書籍資料逐筆以增加table欄位的方式顯示
for (let i in books) {
$('table').append('<tr><td>' + books[i].book + '</td><td>' +
books[i].price +'</td></tr>')
}
}
/*
以上都還只是在宣告函數,下面開始要呼叫函數真正執行了,要注意的是呼叫時傳入的參數內容,若有問題回去上面看看前置觀念,重點在參數的巢狀傳遞,舉個例,把書籍資料存放在叫做books的變數裡傳給了a函數,存放在a一個叫做x的參數裡,a再呼叫b函數,把x傳遞給了b函數的參數y,事實上books,a函數的x參數,b函數的y參數,這三個容器雖然名稱不同,但存放的是同樣的東西,也就是書籍資料,只是層層傳遞,一開始有人把書籍資料放在背包裡運輸,後來給了另一個人,這個人把資料從背包拿出來改放在置物櫃裡,後來又給了另一個人,這個人把書籍資料從置物櫃裡一筆一筆拿出來排列在桌面上給大家看了
*/
$(document).ready(function() {
/*
當頁面讀取完畢,先執行一次getBooks函數,傳入兩個參數值,其中一個是數字1,代表頁籤,另一個是一個接受一個參數的匿名函數,參數是用來接收書籍資料,所以它的參數命名為books,參數名稱可自行定義,總之就是給它一個可以用來放東西的容器,這個函數其實只是一層皮,是包著羊皮的generalList函數,現在回去看看上面getBooks函數定義那邊,你就知道下面這個匿名函數是其實是在取得書籍資料後進行頁面展示用的
*/
getBooks(1, function(books) {
generateList(books)
})
//為兩顆按鈕指定click事件的觸發函數(click事件發生時就會執行)
$('button').click(function() {
//依按的鈕取得定義在按鈕上面的頁籤資料
const page = $(this).data('page')
//執行getBooks函數以抓取該頁籤的書籍資料並展示,
//傳入上面取得的頁籤資料做為第一個參數以進一步組合成完整的網址,
//和一個匿名函數做為第二個參數,這個函數裡面做了兩個動作,
//一是清除舊資料頁面(除了第一次,之後每次按鈕都要先清除舊資料),
//一是產生新資料頁面
getBooks(page, function(books) {
$('table').empty()
generateList(books)
})
})
})
//打完收工
</script>
沒有留言:
張貼留言