回調函數
什么是回調函數:用通俗易懂的白話來說就是一個函數以傳參的方式傳給另一個函數調用 那么這個函數就叫做是回調函數
先來看一個JQuery中的小例子:
$('#btn').click(function(){
console.log(1)
})
上述代碼我們將一個匿名函數作為參數傳遞給了`click`方法。`click`方法會調用(或者執行)我們傳遞給它的函數。這是`Javascript`中回調函數的典型用法,它在`jQuery`中廣泛被使用。
再來看一個Javascript中典型的回調函數的例子:
let arr = ['Russ','James','Harden']
arr.forEach(function(name,index){
console.log(index + 1 + '.' + name)
})
注意,我們又一次將一個匿名函數以參數的方式傳給了`forEach`方法
到目前為止,我們已經將兩個匿名函數做為參數的形式傳遞給了另一個方法。在我們以后看更多的例子或編寫我們自己的代碼的時候我們需要先來了解一下回調函數到底是怎么運行的
回調函數運行原理
因為函數在Javascript中是第一類對象,我們像對待對象一樣對待函數,因此我們能像傳遞變量一樣傳遞函數,在函數中返回函數,在其他函數中使用函數。※當我們將一個回調函數作為參數傳遞給另一個函數是,我們僅僅傳遞了函數定義。我們並沒有在參數中執行函數。我們並不傳遞像我們平時執行函數一樣帶有一對執行小括號()的函數※
需要注意的很重要的一點是回調函數並不會馬上被執行,這個匿名函數稍后會在函數體內被調用,如下:
let arr = ['Russ','James','Harden']
arr.forEach(function(name,index){
console.log(index + 1 + '.' + name)
})
回調函數也是閉包
當我們將一個回調函數作為變量傳遞給另一個函數時,這個回調函數在包含它的函數內的某一點執行,就好像這個回調函數是在包含它的函數中定義的一樣。這意味着回調函數本質上是一個閉包。
正如我們所知,閉包能夠訪問包含它的函數的作用域,因此回調函數能獲取包含它的函數中的變量,以及全局作用域中的變量。
實現回調函數的基本原理
其實實現一個回調函數並不難,但在開始實現和創建回調函數之前,先熟悉幾個實現回調函數的基本原理。
使用命名或匿名函數作為回調
在前面的jQuery例子以及forEach的例子中,我們使用了再參數位置定義的匿名函數作為回調函數。這是在回調函數使用中的一種普遍的魔術。另一種常見的模式是定義一個命名函數並將函數名作為變量傳遞給函數。比如下面的例子:
let allUserName = []
function logName(data){
if(typeof data === 'String' ){
console.log(data)
}else if(typeof data === 'Object'){
for(let item in data){
console.log(data[item])
}
}
}
function getData(options,callback){
allUserName.push(options)
callback(options)
}
getData({name1:"Russ",name2:"Harden"},logName)
我們先創建一個函數名為logName
的普通函數,函數內部的條件是把名字輸出在控制台。然后我們再創建一個函數名為getData
的普通函數,這個函數接收兩個參數,第一個為傳的值,第二個為回調函數。當我們調用getData
方法的時候,我們把函數logName
作為回調函數傳給getData
,所以在getData
執行的時候,logName
將會在其內部被回調
傳遞參數給回調函數
let name3 = "James"
funcrtion getData(options,callback){
allUserName.push(options)
callback(name3,options)
}
既然回調函數在執行時僅僅是一個普通函數,那么我們就能給它傳遞參數。我們能夠傳遞任何包含它的函數的屬性(或者全局屬性)作為回調函數的參數
使用this對象的方法作為回調函數時的問題
當回調函數是一個this對象的方法時,我們必須改變執行回調函數的方法來保證this對象的上下文。否則如果回調函數被傳遞給一個全局函數,this對象要么指向全局window對象、要么指向包含方法的對象。
let clientData = {
id: 100101,
fullName: "",
setUserName:function(firstName,lastName){
this.fullName = fiestName + " " + lastName
}
}
function getData(firstName,lastName,callback){
//doing someting
callback(firstName,lastName)
}
因為函數getData
是一個全局函數,那么它的this
指向的是window
對象,所以在clientData.setUserName
作為回調函數被執行的時候,this.fullName
並不能准確的指向clientData
中的fullName
變量,而它相當於是給window
對象下增加了一個叫做fullName
的字段。
getData("Russell","Westbrook",clientData.setUserName)
console.log(clientData.fullName) //""
console.log(window.fullName) //Russell Westbrook
使用Call和Apply函數來保存this
我們都知道在JavaScript
中每個函數都有兩個叫做call
和apply
的方法,他們的作用就是用來改變this
指向的,他們的區別這里先不說。
apply方法:
function getData(firstName,lastName,callback,callbackObj){
//doing someting
callback.apply(callbackObj,[firstName,lastName])
}
我們增加了新的參數作為回調對象,叫做callbackObj
。我們將clientData.setUserName
方法和clientData
對象作為參數,clientData
對象會被Apply
方法使用來設置this
對象
使用Apply
方法正確設置了this
指向,我們現在正確的執行了clientData.setUserName
回調函數並在clientData
對象中正確設置了fullName
屬性
getData("Russell","Westbrook",clientData.setUserName,clientData)
console.log(clientData.fullName) //Russell Westbrook
回調地獄
什么是回調地獄:人們普遍以javaScript
的執行順序來編寫代碼,在執行異步代碼時,無論以什么順序簡單的執行代碼,通常情況會變成許多層級的回調函數堆積
解決方法: 1.放棄使用匿名函數,給所有的函數都命名,以名字的方式傳遞回調函數;2.代碼簡潔;3.模塊兒化,將重復代碼寫入一個函數體內;4.promise