一、同步和异步

js执行的时候,浏览器会把代码分为同步执行任务和异步执行任务 同步和异步

  • 同步任务:当前任务按顺序执行,如果当前这个任务没有完成,下一个任务不能开始
  • 异步任务:当前任务需要过一点时间或者执行时机不确定(定时器里面的回调就是过一点时间才会执行,事件就是执行时机不确定),浏览器不会傻傻的等着这件事情完成,而是先去做后面的事情,等把后面的事情都做完,再去看这些任务

常见的异步任务

  1. 定时器的回调函数都是异步执行
  2. 所有的事件函数都是异步执行
  3. AJAX 的异步情形( open 的第三个参数 true 就是异步)
  4. 回调函数也可以是异步执行
  1. let n = 0;
  2. setTimeout(() => {
  3. ++n;
  4. }, 1000);
  5. console.log(n); // n 是多少?是0,为啥呢?很显然console.log(n)并没有等着上面的定时器执行,因为如果等了定时器执行,n 就变成了1,输出结果就会变成1,而现在是0,所以没等;
  6. console.log(n);

二、异步编程

异步编程:编写异步处理程序就是异步编程,js 最大的特色就是异步和事件机制 浏览器是如何实现异步的?

  • js 是单一线程的,它一次只能做一件事,能够实现异步,依赖于浏览器的任务队列机制,任务队列分为两种
  • 主任务队列:主任务队列中放的都是当前需要同步执行的任务
  • 等待任务队列:把不需要立即执行的任务都放到等待任务队列中

  • 首先执行主任务队列中的任务,当主任务队列中的任务【都执行完了】,再去等待任务队列看看那些任务可以执行了(等待任务到达了执行条件,例如定时器到时间),如果有多个任务都满足条件了,那么看谁先到达了执行条件,谁就先执行

  • 当主任务队列中的任务从上到下执行的时候,遇到一个异步任务,浏览器不会等着这些异步任务执行,而是把他们添加到等待任务队列中去排队
  • 当主任务队列中的任务执行完成后,才会去等待任务队列(如果主任务队列中的任务执行不完,即使等待任务队列中的任务时间到了,也不会执行)
  • 等待任务队列中谁先到达执行条件了(如果有多个,谁先到,就先执行谁),就把这个任务拿到主任务队列中执行它,等执行完再去等待任务队列找可以执行的任务,再次放到主任务队列……

    1. 冒烟测试

冒烟测试:测试主流程和主要功能,保证其通畅性,冒烟测试通过是基本的准入标准

  1. setTimeout(() => {
  2. console.log('abc');
  3. }, 1000);
  4. while (true) {};
  5. // while (true) {} 是同步任务,死循环,永远执行不完。就是主任务队列中的任务永远执行不完,就永远不会去执行等待任务队列中的任务;即便是过了1s钟,abc也无法打印出来;

普通的 for 循环及 while 循环的死循环:无限制的占用内存,导致电脑卡顿或宕机。 当递归进入死循环:也是死循环,不同的是会用掉内存分配给浏览器的内存,不会造成卡顿及宕机。

2. 那么问题来了

  1. setTimeout(() => {
  2. console.log(1);
  3. }, 2000); // 等待任务
  4. console.log(2);
  5. setTimeout(() => {
  6. console.log(3)
  7. }, 3000);
  8. console.log(4);
  9. setTimeout(() => {
  10. console.log(10)
  11. }, 0);
  12. for(let i = 0; i < 9999999; i++) {}
  13. console.log(5);
  14. setTimeout(() => {
  15. console.log(6)
  16. }, 1400);
  17. console.log(7);
  18. setTimeout(() => {
  19. console.log(8)
  20. }, 1500);
  21. console.log(9); // 输出顺序:
  22. // 2 4 5 7 9 10 6 8 1 3

setTimeout(function () {}, 0) 定时器写0,也不是同步执行,所有的定时器(定时器的回调函数)都是异步的。即使写0,也需要把主任务对列中的同步任务执行完,才回去执行它。即使是主任务队列中的时间无论用的多少,也是需要时间的,这个时间叫做定时器的最小反应时间,如果设置的时间比这个值还小,用的就是最后定时器使用的最小反应时间(这个时间和硬件及操作系统有关系)

三、回调函数

回调函数:把一个函数 A 当作实参传给函数 B,此时 A 叫做回调函数,B 称为宿主函数

  1. let obj = {
  2. id: 17
  3. };
  4. let fn = (callback) => {
  5. // callback 是形参,形参是变量,用来存储值和代表值;形参都是用来代表实参的。所以操作形参就是在操作形参代表的值;
  6. // 1. 回调函数可以执行无限次,并且可以根据需要传实参
  7. callback(1, 2);
  8. callback(2, 3);
  9. callback(4, 6);
  10. callback.call(obj, 8, 10); // 修改回调函数的 this 指向
  11. let res = callback(10, 15); // 获取回调函数的返回结果
  12. console.log('res is ', res);
  13. setTimeout(() => {
  14. callback('x', 'y');
  15. }, 3000);
  16. };
  17. let res2 = fn(function (n, m) {
  18. console.log(n, m);
  19. console.log(this);
  20. return n + m;
  21. }); // 此时小括号里面的 function 就是回调函数,fn 就是宿主函数;

回调函数在宿主函数中的行为

  • 回调函数会根据需要无限次数执行
  • 回调函数不仅可以执行,还可以在宿主函数中执行的时候给它传递实参,但是需要回调函数设置形参或者使用 arguments 或者不定参数接受即可。
  • 回调函数还可以修改this指向
  • 在宿主函数中还可以接收回调函数的返回值
  • 在宿主函数中,回调函数还可以异步执行,在没有 Promise 和 async / await 以及 Generator 函数,都是用回调函数解决异步问题

四、异步的AJAX

  1. let value = null; // 接收数据
  2. let xhr = new XMLHttpRequest();
  3. xhr.open('GET', 'json/data.json', true);
  4. xhr.onreadystatechange = function () {
  5. if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
  6. value = JSON.parse(xhr.responseText);
  7. }
  8. };
  9. xhr.send();
  10. console.log(value); // value 的值是 null,为啥?因为 ajax 现在是异步的,不会等 ajax 完成再执行 console.log(value)这一行;
  11. // ? 怎么办???????????
  12. // 使用回调函数解决这个问题:要使用回调函数首先要创建函数,并且实参需要传函数;
  13. function ajax(callback) {
  14. let xhr = new XMLHttpRequest();
  15. xhr.open('GET', 'json/data.json', true);
  16. xhr.onreadystatechange = function () {
  17. if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
  18. let value = JSON.parse(xhr.responseText);
  19. callback(value); // 在这里执行回调函数,并且把 ajax 拿到的数据作为实参传递给回调函数,此时回调函数执行的时候就能拿到 ajax 获取到的数据;
  20. }
  21. };
  22. xhr.send();
  23. }
  24. function bindHTML() {
  25. // 绑定数据:
  26. }
  27. ajax(function (ajaxData) {
  28. console.log(ajaxData)
  29. });
  30. bindHTML(); // ????? 在这里绑定数据能不能行?那么可以在哪里执行?