一、同步和异步
js执行的时候,浏览器会把代码分为同步执行任务和异步执行任务 同步和异步
- 同步任务:当前任务按顺序执行,如果当前这个任务没有完成,下一个任务不能开始
- 异步任务:当前任务需要过一点时间或者执行时机不确定(定时器里面的回调就是过一点时间才会执行,事件就是执行时机不确定),浏览器不会傻傻的等着这件事情完成,而是先去做后面的事情,等把后面的事情都做完,再去看这些任务
常见的异步任务
- 定时器的回调函数都是异步执行
- 所有的事件函数都是异步执行
- AJAX 的异步情形( open 的第三个参数 true 就是异步)
- 回调函数也可以是异步执行
let n = 0;
setTimeout(() => {
++n;
}, 1000);
console.log(n); // n 是多少?是0,为啥呢?很显然console.log(n)并没有等着上面的定时器执行,因为如果等了定时器执行,n 就变成了1,输出结果就会变成1,而现在是0,所以没等;
console.log(n);
二、异步编程
异步编程:编写异步处理程序就是异步编程,js 最大的特色就是异步和事件机制 浏览器是如何实现异步的?
- js 是单一线程的,它一次只能做一件事,能够实现异步,依赖于浏览器的任务队列机制,任务队列分为两种
- 主任务队列:主任务队列中放的都是当前需要同步执行的任务
等待任务队列:把不需要立即执行的任务都放到等待任务队列中
首先执行主任务队列中的任务,当主任务队列中的任务【都执行完了】,再去等待任务队列看看那些任务可以执行了(等待任务到达了执行条件,例如定时器到时间),如果有多个任务都满足条件了,那么看谁先到达了执行条件,谁就先执行
- 当主任务队列中的任务从上到下执行的时候,遇到一个异步任务,浏览器不会等着这些异步任务执行,而是把他们添加到等待任务队列中去排队
- 当主任务队列中的任务执行完成后,才会去等待任务队列(如果主任务队列中的任务执行不完,即使等待任务队列中的任务时间到了,也不会执行)
- 等待任务队列中谁先到达执行条件了(如果有多个,谁先到,就先执行谁),就把这个任务拿到主任务队列中执行它,等执行完再去等待任务队列找可以执行的任务,再次放到主任务队列……
1. 冒烟测试
冒烟测试:测试主流程和主要功能,保证其通畅性,冒烟测试通过是基本的准入标准
setTimeout(() => {
console.log('abc');
}, 1000);
while (true) {};
// while (true) {} 是同步任务,死循环,永远执行不完。就是主任务队列中的任务永远执行不完,就永远不会去执行等待任务队列中的任务;即便是过了1s钟,abc也无法打印出来;
普通的 for 循环及 while 循环的死循环:无限制的占用内存,导致电脑卡顿或宕机。 当递归进入死循环:也是死循环,不同的是会用掉内存分配给浏览器的内存,不会造成卡顿及宕机。
2. 那么问题来了
setTimeout(() => {
console.log(1);
}, 2000); // 等待任务
console.log(2);
setTimeout(() => {
console.log(3)
}, 3000);
console.log(4);
setTimeout(() => {
console.log(10)
}, 0);
for(let i = 0; i < 9999999; i++) {}
console.log(5);
setTimeout(() => {
console.log(6)
}, 1400);
console.log(7);
setTimeout(() => {
console.log(8)
}, 1500);
console.log(9); // 输出顺序:
// 2 4 5 7 9 10 6 8 1 3
setTimeout(function () {}, 0) 定时器写0,也不是同步执行,所有的定时器(定时器的回调函数)都是异步的。即使写0,也需要把主任务对列中的同步任务执行完,才回去执行它。即使是主任务队列中的时间无论用的多少,也是需要时间的,这个时间叫做定时器的最小反应时间,如果设置的时间比这个值还小,用的就是最后定时器使用的最小反应时间(这个时间和硬件及操作系统有关系)
三、回调函数
回调函数:把一个函数 A 当作实参传给函数 B,此时 A 叫做回调函数,B 称为宿主函数
let obj = {
id: 17
};
let fn = (callback) => {
// callback 是形参,形参是变量,用来存储值和代表值;形参都是用来代表实参的。所以操作形参就是在操作形参代表的值;
// 1. 回调函数可以执行无限次,并且可以根据需要传实参
callback(1, 2);
callback(2, 3);
callback(4, 6);
callback.call(obj, 8, 10); // 修改回调函数的 this 指向
let res = callback(10, 15); // 获取回调函数的返回结果
console.log('res is ', res);
setTimeout(() => {
callback('x', 'y');
}, 3000);
};
let res2 = fn(function (n, m) {
console.log(n, m);
console.log(this);
return n + m;
}); // 此时小括号里面的 function 就是回调函数,fn 就是宿主函数;
回调函数在宿主函数中的行为
- 回调函数会根据需要无限次数执行
- 回调函数不仅可以执行,还可以在宿主函数中执行的时候给它传递实参,但是需要回调函数设置形参或者使用 arguments 或者不定参数接受即可。
- 回调函数还可以修改this指向
- 在宿主函数中还可以接收回调函数的返回值
- 在宿主函数中,回调函数还可以异步执行,在没有 Promise 和 async / await 以及 Generator 函数,都是用回调函数解决异步问题
四、异步的AJAX
let value = null; // 接收数据
let xhr = new XMLHttpRequest();
xhr.open('GET', 'json/data.json', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
value = JSON.parse(xhr.responseText);
}
};
xhr.send();
console.log(value); // value 的值是 null,为啥?因为 ajax 现在是异步的,不会等 ajax 完成再执行 console.log(value)这一行;
// ? 怎么办???????????
// 使用回调函数解决这个问题:要使用回调函数首先要创建函数,并且实参需要传函数;
function ajax(callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'json/data.json', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
let value = JSON.parse(xhr.responseText);
callback(value); // 在这里执行回调函数,并且把 ajax 拿到的数据作为实参传递给回调函数,此时回调函数执行的时候就能拿到 ajax 获取到的数据;
}
};
xhr.send();
}
function bindHTML() {
// 绑定数据:
}
ajax(function (ajaxData) {
console.log(ajaxData)
});
bindHTML(); // ????? 在这里绑定数据能不能行?那么可以在哪里执行?