回调函数
什么是回调函数:用通俗易懂的白话来说就是一个函数以传参的方式传给另一个函数调用 那么这个函数就叫做是回调函数
先来看一个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