什么是异步?什么是同步?
如果能直接拿到结果,那就是同步,比如在医院挂号,拿到号才离开窗口。同步任务可能消耗10毫秒,也可能需要3秒。
如果不能直接拿到结果,那就是异步,比如在餐厅门口等位,拿到号可以先去逛街,那什么时候才能真正吃饭呢?可以每10分钟去餐厅问一下(轮询),也可以扫码用微信接收通知(回调)。
异步举例
var request = new XMLHttpRequest()
request.open('GET', '/a/b/c?name=ff', true);
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200) {
console.log(request.responseText);
}
};
request.send();
以 AJAX 为例,request.send() 之后,并不能直接得到 response,必须等到 readyState 变为4后,浏览器回头调用 request.onreadystatechange 函数,我们才能得到 request.response,这跟餐厅发送微信提醒的过程是类似的。
什么是回调
- 写给自己用的函数,不是回调
- 写给别人用的函数,就是回调
写了却不调用,给别人调用的函数,就是回调。request.onreadystatechange 就是写给浏览器调用的,意思就是浏览器回头调用一下这个函数。
回调举例
function f1() {}
function f2(fn) {
fn()
}
f2(f1)
分析:f1 自身没有调用而是传给了 f2(别人),f2 调用了 f1,也就是说 f1 是写给 f2 调用的函数,所以,函数 f1 是回调。
异步和回调的关系
异步和回调的关联:
异步任务需要在得到结果时通知 JS 来拿结果,怎么通知呢?
可以让 JS 留一个函数地址(电话号码)给浏览器,异步任务完成时浏览器调用该函数地址即可(拨打电话),同时把结果作为参数传给该函数(电话里说可以来吃了),这个函数就是写给浏览器调用的,所以是回调函数。
异步和回调的区别:
- 异步任务需要用到回调函数来通知结果(不一定非要用回调,还可以用轮询)
- 但回调函数不一定只用在异步任务里,回调可以用到同步任务里,比如
array.forEach(n => console.log(n))就是同步回调
判断同步和异步(根据特征或文档)
如果一个函数的返回值处于 setTimeout、AJAX(即 XMLHttpRequest)、AddEventListener 这三个东西内部(不限于这三种),那么这个函数就是异步函数。
request.open(method, url, false) 可以把 AJAX 设为同步的,但是一般不要把 AJAX 设置为同步的,这样做会使请求期间页面卡住。
异步函数的例子——摇骰子
function 摇骰子() {
setTimeout(() => { //箭头函数
return parseInt(Math.random() * 6) + 1
}, 1000)
//return undefined
}
const n = 摇骰子()
console.log(n) //undefined
摇骰子() 没有写 return,那就是 return undefined,箭头函数里有 return,返回真正的结果,所以这是一个异步函数/异步任务。
那怎么拿到异步结果?
可以用回调。写个函数,然后把函数地址给他。
function f1(x) {console.log(x)}
摇骰子(f1)
然后要求摇骰子函数得到结果后把结果作为参数传给 f1.
function 摇骰子(fn) {
setTimeout(() => {
fn(parseInt(Math.random() * 6) + 1)
}, 1000)
}
由于 f1 声明后只用了一次,所以可以删掉 f1,并简化为箭头函数。
function f1(x) {console.log(x)}
摇骰子(f1)
//改为
摇骰子(x => console.log(x))
//再简化为
摇骰子(console.log) //如果参数个数不一致就不能这样简化
摇骰子例子的完整代码:
function 摇骰子(fn) {
setTimeout(() => {
fn(parseInt(Math.random() * 6) + 1)
}, 1000)
}
摇骰子(x => console.log(x)) //打印出 1-6 的随机数
总结
- 异步任务不能拿到结果
- 于是我们传一个回调给异步任务
- 异步任务完成时调用回调
- 调用的时候把结果作为参数
ps 面试题:下面代码打印出的结果是什么?
const array = ['1', '2', '3'].map(parseInt)
console.log(array)
答:[1, NaN, NaN](箭头函数的错误简化问题)
上面代码简化前的写法是这样的:
const array = ['1', '2', '3'].map((item, i, arr) => {
return parseInt(item, i, arr) //parseInt只接收两个参数,所以 arr 被忽略
}
//parseInt('1', 0, arr) => 1(0为无效参数,所以结果为1)
//parseInt('2', 1, arr) => NaN(把'2'作为一进制数进行解析,一进制只有0,所以结果为NaN)
//parseInt('3', 2, arr) => NaN(把'3'作为二进制数进行解析,二进制只有0和1,所以结果为NaN)
console.log(array) //[1, NaN, NaN]
正确写法:
const array = ['1', '2', '3'].map(item => parseInt(item))
console.log(array) //[1, 2, 3]