彻底理清JavaScript的单线程,异步,Event Loop,Promise的关系

JavaScript的三座大山:单线程与异步,原型与原型链(继承),作用域和闭包。
接下来就其中的单线程与异步,和延扩涉及的事件轮询,Promise写下我个人的理解,算是做下总结,顺便给对这几个概念的关系有些模糊的朋友提供一些思路。
若有错误的,敬请指正。

JS的单线程

其他面向对象语言JAVA,C++,都是多线程的,即一个进程中可以并发多个线程,而每条线程并行执行不同的任务,也就是说在同一时刻可以同时进行多个任务
而单线程:没有多个线程可供主程序来调用,简单来说,就是同一时刻只能做一件事情
JS正是单线程的语言

console.log("abc")
alert("小鱼你好")
console.log("e")

运行结果只打印出了“abc”,对话框点确定之前,“e”是不会被打印的,说明JS是顺序执行的,并且前面的代码执行完才能继续执行后面的代码,没有其它的多余线程可以在执行第二行代码的时候同时执行第3行

为什么JS是单线程的

JS语言的应用场景注定了它只能是单线程的语言,可以说,单线程是JavaScript的本质
原因就在于:

避免dom渲染冲突

JavaScript是一种属于网络的脚本语言,已经被广泛用于Web应用开发,最早就是在HTML网页上使用,进行网页的交互功能

我们都知道浏览器利用HTML文件内容可以渲染dom结构,JS也可以操作dom结构,则两者都可以对dom结构产生影响。
所以为了避免出现二者都对同一dom同时操作而造成渲染冲突,需要

  1. JS代码执行的时候,浏览器的渲染会暂停
  2. 两端JS不能同时执行(否则有多个源头修改dom,会产生冲突)

所以:
JS本身必须是单线程的,且必须和浏览器渲染共用一个线程

JS的异步:单线程的解决方案

为什么要使用异步

上面说了由于JavaScript用于为网页添加各式各样的动态功能,能够操作dom,为了避免dom渲染冲突,所以JavaScript必须是单线程的。
但是单线程又会带来一系列的问题,比如卡顿,即前面的代码没有执行完,后面的代码又只能一直等待
比如定时器任务setTimeout/serInteral

var i,sum = 0;
for(i=0;i<1000000000;i++){
    sum+=i
}
//循环执行期间,JS执行和dom渲染暂时卡顿
console.log("abc") 
//由于前面的代码没有执行完,后面的代码也只能是一直等待着,即没有“abc”被打印出

为了解决单线程带来的问题,有了异步这个解决方案
在可能需要等待的情况下,为了不让这些等待的过程像alert程序一样阻塞程序,这时候就需要异步了,即:
所有“等待的情况”都需要异步
故需要“等待”的情况也是通常前端使用的异步的场景:

  1. 定时任务: setTimeoutsetInvervalprocess.nextTicksetImmediate(node特有的定时器)
  2. 网络请求:ajax请求,动态<img>加载
  3. 事件绑定(比如点击事件)
// 有定时器任务
console.log("aaa")
setTimeout(function(){   //反正1s后才执行,先不管它,先让其他的代码执行
    console.log("bbb") 
},1000)
console.log("ccc")
console.log("ddd")

运行结果:


在这里插入图片描述
console.log("aaa")
$.ajax({        
    url:'xxxxxx',
    success:function(result){  //ajax加载完才执行
        console.log(result)    //先不管它,先让其他JS代码执行
    }
})
console.log("ccc")
console.log("ddd")

运行结果是aaa,ccc,ddd,之后才是ajax请求返回的内容

异步的实现机制---Event Loop事件轮询

JavaScript是怎么实现可以不依照代码顺序执行,实现部分代码(也就是异步任务)的异步执行的呢?
就是通过eventLoop即事件轮询的机制。
换句话说,event loop是JavaScript实现异步的具体方案
event loop 机制的核心:

  1. 代码分为同步代码异步代码(异步任务会有对应的回调函数)
  2. 同步代码放在主执行栈里,直接执行
  3. 异步函数先放在异步任务队列里,暂时先不执行
  4. 待同步函数执行完毕,轮询执行异步队列里的函数(执行的就是相关异步任务对应的回调函数)

第3点 将异步任务放入任务队列时分为三种情况:

1.若异步任务没有延时,则直接将其放入异步队列

2.若有延时,则等延时时间到了才会放入异步队列

3.若有ajax请求,则等ajax加载完成才放入异步队列

第4点 “轮询”过程理解:
JS搜索引擎会轮询监听异步队列里的函数,主要主线程里空了(即主执行栈里的同步代码都执行完了),就会去读取任务队列里的事件,调到主线程里执行,这个过程是循环重复

// 代码演示
$.ajax({
   url:'xxxxx',
   success:function(result) {
        console.log('a')
   }
})
setTimeout(function () {
     console.log('b')
},100)
setTimeout(function () {
    console.log('c')
)
console.log('d')

分析:


在这里插入图片描述

运行结果是dcba或dcab
(若该ajax加载完成时间小于100ms,则ajax的回调函数的执行先于延时100ms的定时器的回调函数,则'a'会先于'b'打印)

微任务和宏任务

异步任务又分为“微队列”和“宏队列”里的任务,微队列里的都执行完才会去执行宏队列里的异步任务
微队列:

宏队列:

  • setTimeout
  • setInterval
  • setImmediate(Node独有)
  • requestAnimationFarme(浏览器独有)
  • I/O
  • UI rendering(浏览器独有)

常用的异步任务和执行顺序整理在下图(图略丑...)

在这里插入图片描述

其中要特别注意的是promise对象一旦建立就执行,只不过promise对象的then方法是异步的

//直接简单粗暴的用下面简单代码演示
console.log('a'))
setImmediate(() => console.log('b'));
new Promise((resolve, reject) => {console.log('c');resolve()}).then(() => 
Promise.resolve().then(() => console.log('d'));
--------------------- 
// 结果: a c d b 

JS的promise:异步的解决方案

由于JS单线程的本质,需要通过异步来解决单线程带来的问题。
而异步也会带来问题:
1.代码没按照书写形式执行,导致可读性变差

  1. callback(回调函数)中不容易模块化

回调嵌套是解决异步最直接的方法,即将后一个的操作放在前一个操作的异步回调里,但回调的多层嵌套会导致使代码很冗杂,而且导致检查代码会费劲。
Promise可以用来优雅避免callback hell问题

promise的基本语法

通过new Promise()生成一个promise实例(promise对象),传入一个函数,函数有两个参数,
第一个为resolve,第二个参数为reject,这两个参数都是函数形式,分别是成功和失败时执行的函数
promise对象可调用then方法,then方法可传入两个参数,
第一个参数:成功时的回调,第二个参数:失败时的回调

注意:
当then()没有return时则默认返回的仍是调该then方法的promise对象
当then()里有return则返回的是指定的promise对象

  function loadImg(src) {
        return new Promise(function (resolve, reject) {
            var img = document.createElement('img')
            img.onload = function () {
                resolve(img)
            }
            img.onerror = function () {
                reject()

            }
            img.src = src
        })
    }
    var src = "xxxxx"
    var result = loadImg(src)  //result是调用loadImg函数后返回的promise对象
    result.then(function (img) {  //promise调用then方法,传入两个参数
        console.log(img.width)
    },function () {
        console.log('failed')
    }).then(function (img) {  //可多次调用then方法
       console.log(img.height)
    })

promise捕获异常

这里顺便也附上如何用promise捕获异常
想要捕获异常时:

1. then方法只传入一个参数:成功时的回调函数(不再传入失败时的回调)
2. 最后统一用catch方法捕获异常:catch方法传入一个函数,函数的参数就是想要捕获的产生异常的对象

 var src='xxxxx'
    var result=loadImg(src)
    result.then(function (img) {
        console.log(1,img.width) 
    }).then(function (img) {
        console.log(2.img.height)
    }).catch(function (ex) { //catch传入的函数的参数就是产生异常的那个对象
        // 统一捕获异常
        console.log(ex)
    })

总结

  • 为了避免dom渲染冲突,要求JavaScript的本质就是单线程

  • 为了解决单线程带来的可能造成卡顿和等待的问题,需要JavaScript的异步

  • 为了实现JavaScript的异步,利用的是Event Loop 事件轮询的机制

  • 为了解决异步里回调函数嵌套带来的问题,利用Promise 优雅避免callback hell问题

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

推荐阅读更多精彩内容