js异步执行原理&浅谈异步与回调

1 篇文章 0 订阅
1 篇文章 0 订阅

先说一件大家都知道的一句话

javaScript语言的一大特点是单线程,单线程就是一次他只能做一件事~

其实我也在学js的时候,在很多博客都看过这句话,好像是一眼就看懂了,但是一直不知道这句话到底是干嘛的,到底有什么用~,所以就注定有些bug还是要踩一次.

第一个因为js是单线程的困惑来自于WebApi的动态创建元素, 对,异步并不是我在ajax里面学习到的,而是DOM里面学习到的,但是ajax让我更深的去理解了什么是异步

先看下第一段让我困惑,但是其实非常简单的异步代码,把核心逻辑简化后的代码(其实很简单,每个人都看得懂)

<div>
  <ul>
  <li>我是第一个li</li>
  <li>我是第二个li</li>
  </ul>
  <input id="btn" type="button" value="动态生成一个li">
 </div>
  var li = document.getElementsByTagName('li');
 var btn = document.getElementById(('btn'));
 ​
 //动态创建一个li元素
 btn.onclick = function () {
  var li = document.createElement('li');
  li.innerHTML="我是动态创建的li";
  document.getElementsByTagName('ul')[0].appendChild(li);
 };
 //给每一个li元素注册点击事件,当点击的时候,让背景颜色变红
 for (let i = 0; i < li.length; i++) {
  li[i].onclick = function () {
  li[i].style.backgroundColor ='red';
  }
 }
 ​
 console.log(1);
 setTimeout(function () {
  console.log(2)
 }, 0);
 console.log(3);
 //输出结果1 3 2

执行的结果却是,动态创建的li标签,没有点击事件…

image.png

image.png

上面这个问题其实对于一个不了解什么是异步的小白来说,真的是一个很大的问题,拿到这个问题后,我开始了一大串的百度,最后,问题没有得到实质的解决,反而拿到一个新的概念,叫"异步";

什么是异步任务?

简单的理解就是,js中所有的代码块都可以按照任务分为两种任务,一种是同步任务,一种是异步任务,而js遇到这两种任务的时候,会按照同步和异步两种类别进行区别对待.

同步任务进入主线程,从上往下执行,一条一条代码执行,形成一个叫执行栈的东西

异步任务会进入另外一个任务队列中,要等待主线程执行完了,才会执行,

异步任务包括像需要用户触发的点击事件,滚动事件,键盘事件,定时器等等,我喜欢简单的去理解 ,我的理解是未来才会发生的事件就是异步事件

听起来肯定还是有些复杂,很抽象,我们用一张图来表示

image.png

  • 看到上面这张图中,左边的同步任务非常好理解,那就来说一下右边的异步任务;

  • 当事件被识别为异步任务的时候,浏览器会把这个任务放在 Event Table

  • 这个Event Table会把传入的异步事件注册为一个回调函数,然后传给Event Queue,

  • 事件注册为回调函数后,并放在Event Queue中等待,那什么时候这个回调函数会被执行呢?

  • js中引擎中有一个monitoring process进程,在主线程执行完毕后 , 会不断的检查Event Queue有没有回调函数在等待执行,如果有,就把这个回调函数放在主线程上执行

  • 而这个异步任务不断排队,主线程不断检查异步排队,主线程不断执行的循环就叫事件循环 Event Loop

了解了事件循环,再来看上面的代码,就能够发现问题的原因:

1.定时器的时间就算是0.他也是一个异步任务,HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加

2.动态创建元素的问题

 //动态创建一个li元素
 btn.onclick = function () {
  var li = document.createElement('li');
  li.innerHTML="我是动态创建的li";
  document.getElementsByTagName('ul')[0].appendChild(li);
 };
 //给每一个li元素注册点击事件,当点击的时候,让背景颜色变红
 for (let i = 0; i < li.length; i++) {
  li[i].onclick = function () {
  li[i].style.backgroundColor ='red';
  }
 }

1.btn.onclick = function(){} 已经被注册成一个回调函数,等待在事件队列中,注册的回调函数中li没有点击事件,此时for循环已经结束

知道原理后其实简单的修改一下就可以了,原理就是虽然你在队列中排队,但是点击事件已经注册在排队里的回调函数里面的, 把事件注册在了未来

 function fn() {
  var li = document.createElement('li');
  li.innerHTML = "我是动态创建的li";
  document.getElementsByTagName('ul')[0].appendChild(li);
  li.onclick = function () {
  li.style.backgroundColor = 'red';
  }
 }
 btn.onclick = fn;

知道异步后有什么用?

了解异步之后,对于回调函数的学习能起到一个顺水推舟的作用,而回调函数是js语言中非常重要的一个东西, 可以说是js中的精髓了, 异步是未来才会执行的事件,而回调函数专门用来接收未来才会有结果的数据.一张图来解释~
image.png

我们知道在ajax中请求拿到的结果是一个异步操作,而这个结果,是我在未来才能拿到的一个结果,而且作为函数的封装者,这个未来的结果,我不能自己用, 而是要把未来的结果给别人用 . 这就让人很蒙圈 .话不多数,我们先来看下jQuery封装的ajax

$.ajax({
  type:'get',
  url:"",
  data:{},
  success:function (data) {
  //我的data是未来(请求成功后)才会有结果是数据
  console.log("成功的回调函数")
  }
  })

success是一个异步结果,我们把他按照异步的原理简化一下

 //假设fn是一个帮我在服务器端拿数据的一个函数,拿到后他不能自己处理,因为他的作用是帮我拿数据
 function fn(a,callback){
  //我是一个成功后的回调异步结果
  setTimeout(function () {
  //我是回调函数,我把未来才会产生的结果作为参数传给了我的调用者
  a = 500;
  callback(a);
  },100)
 }
 //我需要用一个数据,但是这个数据我在未来才能用的到,我传入一个回调函数,他帮我带来了未来的结果
 fn(100,function (data){
  var b = 1000;
  console.log(data + b); //1500
 })
  • 第一次js执行,fn调用,传参100,并在第二个参数里声明了一个匿名函数,此时主线程执行完毕

  • 事件队列100毫秒后,把a=500;作为参数传入回调函数,同时调用回调函数

  • fn中的第二个参数拿到实参并执行回调代码

  • 这样callback就完成了他的使命,拿到异步结果,把结果作为参数给调用者进行运算

最后有兴趣的再看下源生的封装

 function ajax (method, url, params, done) {
  method = method.toUpperCase();
  var xhr = new XMLHttpRequest();
  if (typeof params === 'object') {
  var tempArr = []
  for (var key in params) {
  var value = params[key]
  tempArr.push(key + '=' + value)
  };
  params = tempArr.join('&');
  };
 ​
  if (method === 'GET') {
  url += '?' + params
  };
 ​
  xhr.open(method, url, false);
  var data = null;
  if (method === 'POST') {
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
  data = params
  };
  xhr.onreadystatechange = function () {
  if (this.readyState !== 4) return
  //this,responseText是未来的结果
  //被done回调函数带走了
  done(this.responseText);
  };
  xhr.send(data);
  };
 // 调用者============================
 ajax('get', 'time.php', {}, onDone(a){})</pre>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值