黑马程序员技术交流社区

标题: 【上海校区】如何验证JavaScript是单线程? [打印本页]

作者: 没名字i    时间: 2020-2-13 11:07
标题: 【上海校区】如何验证JavaScript是单线程?
进程与线程
所谓进程,简单理解就是程序的一次执行过程,它占有独有的一片内存空间,而线程是CPU的基本调度单位。
一个进程中一般至少有一个运行的线程:主线程,
一个进程中也可以同时运行多个线程,这时程序就是多线程,
一个进程中的数据可以供其中多个线程共享,
多个进程之间的数据不能直接共享。
前端开发自然离不开浏览器,目前大部分新版浏览器都是多进程的,老版本大多数都是单进程的,同时浏览器都是多线程的。
验证JavaScript是单线程
直接上代码
[AppleScript] 纯文本查看 复制代码
    let nowTime =  Date.now()
    console.log("现在的时间:" + nowTime) // 1581391609874
    setTimeout(function(){
        console.log("之后的时间:" + (Date.now() - nowTime)) // 116
    }, 100)
    for (let i = 0; i < 9999; i++) { // 耗时任务
    }
   
通过代码可以看出,首先输出当前时间nowTime,然后通过定时器延迟100毫秒后输出延迟后的时间与nowTime的时间差。按照定时器的设定,他们的差值应该为100,但实际输出为116,这是为什么?
这是因为JavaScript引擎执行代码的基本流程是先执行初始化代码,包括一些特别的代码,比如设置定时器,绑定监听,发送ajax等。等初始化代码执行完成后才可能执行回调代码(异步)。
上述代码先执行除了定时器回调function以外的代码,然后再执行function,导致执行过程在for循环中浪费了部分时间,等for循环执行完后才会执行定时器回调function,所以才会出现16毫秒的偏差。
而如果是多线程的话,当遇到定时器或者耗时任务时,可能会再次开启一个线程去单独执行对应的定时器或耗时任务,肯定不会像JavaScript一样出现阻塞,这个例子可以验证JavaScript是单线程。
再看一个例子
    setTimeout(function(){
        console.log("1s后执行")
    }, 1000)
    function func() {
        console.log("func()")
    }
    func() //  func()
    alert('阻断')
    console.log(123123)

复制代码执行上述代码会发现,当通过alert阻断执行后,只要不点掉alert弹窗,后续任何任务都不会执行,包括定时器回调。通过这个例子也可以看出JavaScript是单线程的, 一旦一个任务阻断卡死了,后续的都不会执行。
为什么JavaScript要采用单线程?
了解了如何验证JavaScript是单线程以后,已知单线程如果当前任务没有执行完成,后续任务会被阻断,不会执行,那为什么JavaScript还要采用单线程呢?
其实这个用一个场景就可以解答,首先JavaScript尽管可以作用于服务器(Node.js),但是它的设计初衷是作用在浏览器中的脚本语言,所以在浏览器中操作DOM时,如果JavaScript是多线程的就可能出现多个线程操作同一个DOM的情况,比如线程1删除了该DOM,而线程2修改了该DOM,两个冲突的命令会导致浏览器不知所措,所以将JavaScript设计成单线程可以完全避免这个问题。
单线程如何实现异步?
这就要了解一下JavaScript的事件循环模型。
JavaScript引擎在执行代码的时候会先初始化执行代码,也就是同步代码,包括绑定DOM事件监听,设置定时器,发送Ajax请求等,当初始化完成后会交给浏览器对应的管理模块处理,当事件发生时,管理模块会通知JavaScript引擎将对应的回调函数及其数据添加到回调队列中,当所有初始化代码执行后,才会遍历读取回调队列中的回调函数执行。
用个场景简单描述一下,比如我们需要一个点击操作和一个定时器操作,那么JavaScript会先初始化这些代码,绑定一个click事件,设置定时器,初始化后发送任务交给浏览器,因为浏览器是多线程的,所以浏览器会用多线程处理这些任务。
当click事件发生或定时器启动运行时,浏览器对应的事件管理模块和定时器模块会通知JavaScript引擎将对应的回调函数及其数据添加到回调队列中,当初始化代码执行完成后,就会遍历读取回调队列中的回调函数执行,如果碰上最开始那个for循环的例子,可能就会需要一定时间。
关于setTimeout严谨地说法
所以,最开始的这个例子
[AppleScript] 纯文本查看 复制代码
   let nowTime =  Date.now()
    console.log("现在的时间:" + nowTime) // 1581391609874
   
    setTimeout(function(){
        console.log("之后的时间:" + (Date.now() - nowTime)) // 126
    }, 100)
   
    for (let i = 0; i < 9999; i++) { // 耗时任务
    }

其中setTimeout按照一般的说法是3s后执行function回调函数,这样说其实是不准确的,严谨地解释应该是3s后setTimeout中的回调函数会被推入回调队列中,而回调队列中的回调函数需要初始化代码for循环执行完成后才会执行,所以才会造成时间差不准确。

链接:https://juejin.im/post/5e416c2a6fb9a07c8b5ba46d





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2