内容简介:我们之前介绍了javascript异步的相关内容,我们知道javascript以同步,单线程的方式执行主线程代码,将异步内容放入事件队列中,当主线程内容执行完毕就会立即循环事件队列,直到事件队列为空,当用产生用户交互事件(鼠标点击,点击键盘,滚动屏幕等待),会将事件插入事件队列中,然后继续执行。处理异步逻辑最常用的方式是什么?没错这就是我们今天要说的---回调如你所知,函数是对象,所以可以存储在变量中,
我们之前介绍了javascript异步的相关内容,我们知道javascript以同步,单线程的方式执行主线程代码,将异步内容放入事件队列中,当主线程内容执行完毕就会立即循环事件队列,直到事件队列为空,当用产生用户交互事件(鼠标点击,点击键盘,滚动屏幕等待),会将事件插入事件队列中,然后继续执行。
处理异步逻辑最常用的方式是什么?没错这就是我们今天要说的---回调
js回调函数
如你所知,函数是对象,所以可以存储在变量中,
所以函数还有以下身份:
- 可以作为函数的参数
- 可以在函数中创建
- 可以在函数中返回
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在计算机程序设计中,回调函数,或简称回调(Callback 即call then back 被主函数调用运算后会返回主函数),是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。
回调函数,几乎每天我们都在用
setTimeout(() => {
console.log("这是回调函数");
}, 1000);
const hero=['郭靖','黄蓉']
hero.forEach(item=>{
console.log(item);
})
回调函数解决了哪些问题
举一个简单的:
let girlName = "裘千尺"
function hr() {
girlName = "黄蓉"
console.log(`我是${girlName}`);
}
function gj() {
console.log(`${girlName}你好,我是郭靖,认识一下吧`);
}
hr()
gj()
输出,重点看输出顺序
//=>我是黄蓉 //=>黄蓉你好,我是郭靖,认识一下吧
上面的代码输出是没什么悬念的,不存在异步,都单线程同步执行,最后郭靖和黄蓉相识
如果这时候黄蓉很忙,出现了异步,会怎么样?
let girlName = "裘千尺"
function hr() {
setTimeout(() => {
girlName = "黄蓉"
console.log('我是黄蓉');
}, 0);
}
function gj() {
console.log(`${girlName}你好,我是郭靖,认识一下吧`);
}
hr()
gj()
输出,重点看输出顺序
//=>裘千尺你好,我是郭靖,认识一下吧 //=>我是黄蓉
虽然定时器是0ms,但是也导致了郭靖和黄蓉的擦肩而过,这不是我们期望的结果,hr函数存在异步,只有等主线程的内容走完,才能走异步函数
所以最简单的办法就是使用回调函数解决这种问题,gj函数依赖于hr函数的执行结果,所以我们把gj作为hr的一个回调函数
let girlName = "裘千尺"
function hr(callBack) {
setTimeout(() => {
girlName = "黄蓉"
console.log('我是黄蓉');
callBack()
}, 0);
}
function gj() {
console.log(`${girlName}你好,我是郭靖,认识一下吧`);
}
hr(gj)
输出,重点看输出顺序
//=>我是黄蓉 //=>黄蓉你好,我是郭靖,认识一下吧
:warning::当回调函数作为参数时,不要带后面的括号!我们只是传递函数的名称,不是传递函数的执行结果
上面小栗子貌似的很简单,我们继续
嵌套回调和链式回调
我们把昨天的demo做一下升级
引入了lodash:处理按钮点击防抖
axios,集成了promis,但promise不是我们今天讨论的内容,我们只使用axios的ajax请求接口功能
easy-mock:接口数据,用来实现ajax请求(数据是假的,但是请求是真的)
嵌套回调
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>javascript回调</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.bootcss.com/lodash.js/4.17.11/lodash.min.js"></script>
</head>
<body>
<button>点击</button>
<script>
{
const btn = document.querySelector('button')
btn.onclick = () => {
_.debounce(() => {
axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock')
.then(data => {
console.log("ajax返回成功");
myData = data.data
console.log(myData);
})
.catch(error => {
console.log("ajax返回失败");
})
}, 500)()
}
}
</script>
</body>
</html>
仔细看代码,不难发现,这是一个典型的 嵌套回调 ,我们分析一下
第一层异步,用户交互,来自按钮的点击事件
第二层异步,按钮去抖,来自lodash下debounce的500ms延时
第三次异步,ajax请求,处理后台接口数据
拿到数据后我们没有继续做处理,在实际工作中可能还存在异步,还会继续嵌套,会形成一个三角形的缩进区域
再继续嵌套,就会形成所说的“回调地狱”,就是回调的层级太多了,代码维护成本会高很多
上面的栗子最多算是入门毁掉地狱,我们看一下这个
function funA(callBack) {
console.log("A");
setTimeout(() => {
callBack()
}, 10);
}
function funB() {
console.log("B");
}
function funC(callBack) {
console.log("C");
setTimeout(() => {
callBack()
}, 100);
}
function funD() {
console.log("D");
}
function funE() {
console.log("E");
}
function funF() {
console.log("F");
}
//从这里开始执行
funA(() => {
funB()
funC(() => {
funD()
})
funE()
})
funF()
(这段代码,带回调的都是异步逻辑)你能很快的看出这段代码的执行顺序吗?
顺序如下:A、F、B、C、E、D
一般正常人不会这么嵌套多层,层级一多,就会考虑拆分
链式回调
const btn = document.querySelector('button')
//监听按钮点击事件
btn.onclick = () => {
debounceFun()
}
//去抖动
const debounceFun = _.debounce(() => {
ajax()
}, 500)
//ajax 请求
const ajax = function () {
axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock')
.then(data => {
console.log("ajax返回成功");
myData = data.data
console.log(myData);
})
.catch(error => {
console.log("ajax返回失败");
})
}
我相信很多人都会通过这种 链式回调 的方式处理异步回调,因为可读性比嵌套回调要搞,但是维护的成本可能要高很多
上面的栗子,三个异步函数之间只有执行顺序上的关联,并没有数据上的关联,但是实际开发中的情况要比这个复杂,
回调函数参数校验
我们举一个简单的栗子
let girlName = "裘千尺"
function hr(callBack) {
setTimeout(() => {
girlName = "黄蓉"
console.log('我是黄蓉');
callBack(girlName)
}, 0);
}
function gj(love) {
console.log(`${girlName}你好,我是郭靖,认识一下吧,我喜欢${love}`);
}
hr(gj)
gj作为hr的回调函数,并且hr将自己的一个变量传递给gj,gj在hr的回调中执行,
仔细看这种写法并不严谨,
如果gj并不只是一个function类型会怎么样?
如果love的实参并不存在会怎么样?
况且这只是一个简单的栗子
所以回调函数中,参数的校验是很有必要的,回调函数链拉的越长,校验的条件就会越多,代码量就会越多,随之而来的问题就是可读性和可维护性就会降低。
还是回调函数的校验
但我们引用了第三方的插件或库的时候,有时候难免要出现异步回调的情况,一个栗子:
xx支付,当用户发起支付后,我们将自己的一个回调函数,传递给xx支付,xx支付比较耗时,执行完之后,理论上它会去执行我们传递给他的回调函数,是的理论上是这样的,我们把回调的执行权交给了第三方,隐患随之而来
第三方支付,多次调用我们的回调函数怎么办?
第三方支付,不调用我们的回调函数怎么办?
当我们把回调函数的执行权交给别人时,我们也要考虑各种场景可能会发生的问题
总结一下:
回调函数简单方便,但是坑也不少,用的时候需要多注意校验
以上所述就是小编给大家介绍的《javascript异步中的回调》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Practical Django Projects, Second Edition
James Bennett / Apress / 2009 / 44.99
Build a django content management system, blog, and social networking site with James Bennett as he introduces version 1.1 of the popular Django framework. You’ll work through the development of ea......一起来看看 《Practical Django Projects, Second Edition》 这本书的介绍吧!