一、同步和异步
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(); // ????? 在这里绑定数据能不能行?那么可以在哪里执行?
