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

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

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

</pre>

执行的结果却是,动态创建的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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容