单线程的js是如何工做的

JavaScript的任务队列

js在诞生初期就肯定了其单线程的定位,也就是说,全部任务须要排队,前一个行行执行,前面的执行完,才会执行后面的。若是前一个任务耗时很长,后一个任务就不得不一直等着。
在讨论单线程的js时,咱们先来看看为何js要是单线程的。让js变成多线程的不行么🙄~~
css

为何js是单线程

  1. js诞生初只是为了实现一些简单的表单验证,也就不必太强大。也许一个项目也就几十行js代码。那为啥如今js愈来愈庞大,浏览器厂商不给改改呢?让咱们来看第二点。
  2. 做为一个脚本语言,JavaScript的主要用途是与用户互动,好比操做DOM,那么问题来了,若是有JavaScript有两个线程,这里两个线程同时改变一个div的背景色,那浏览器听谁的,好像两个线程都没问题...
  3. 虽然js是线程的,但浏览器是多线程

有个叫 Web Worker 的东西,可让浏览器开出一个Worker 线程,经过接受发送消息和主线程交互,而且和主线程不冲突的执行。web worker的定位是负责须要大量计算的代码执行线程。它限制了咱们不能访问 DOM 对象。
html

写了半天怎么能没有代码呢..

var a = 0
    while (a < 10000000) {
        a++
    }
    console.log(10000000)
    console.log(123)
    // 123
    // 321
复制代码

看上面这个代码,先执行while 循环,在打印 a,最后123,这很好理解,js就是单线程,一行一行解释执行。就算while 要加一个亿,后面的代码也得等着java

事件监听

那咱们的代码是否是就没办法异步执行了呢?固然不是。咱们的代码须要承担一个很是重要的任务就是,完成和用户的交互。好比你点击一个按钮但愿它能给你弹出一个弹窗。web

// 来看个很简单的例子
    const body = document.querySelector('body')
    console.log(1)
    body.addEventListener('click',function(){
        console.log(body) 
    })
    body.addEventListener('click',function(){
        console.log(body)   
    })
    console.log(2)
    console.log(2)
    console.log(2)
    // 下面能够有无限代码
    ...
复制代码

这段代码。在咱们不点击页面的时候,是不会打印body的。这就是事件监听经过事件监听咱们能够实现,一个异步任务。
ajax

写着写着感受仍是须要简单的说一个ur从输入框到加载完成的主要流程。浏览器

  1. 用户abcd输入一个url,而后浏览器发起DNS查询请求(dns就是把abcd和真实的ip地址1234 对应起来的服务器)
  2. 找到了1234,浏览器就向1234发起创建TCP链接(tcp是一些列协议,其中http是它应用层上的一个,感兴趣的同窗能够搜索一下TCP七层模型,也有叫五层的)
  3. 发送HTTP 请求(http携带报文给服务器,请求行、请求头、请求体)
  4. 浏览器解析http返回的东西(咱们就说一个html吧)
  5. 浏览器解析html从上到下

这里每一个展开均可以写N+1盘文章。咱们重点了解一下最后一个,浏览器解析html的时候会从上到下。在没有碰到js代码以前,浏览器会一边解析css一边解析dom,最后把他们合并渲染。若是中途遇到了js,浏览器会中断解析dom,去解析js,而后从新解析dom,渲染。
服务器

咱们再重点了解一下浏览器对js的解析
网络

遇到script,开启一个宏任务吧。解析里面的js,预解析var和function声明的东西。值得注意的是JavaScript预解析不仅是在一开始。每一个执行上下文都会进行一次预解析。
多线程

function fun () {
        console.log(a)
        var a = 1
    }
    fun() // undefined
复制代码

为啥会是undefined,而不是由于找不到a报错呢?缘由就是js预解析了一次,实际执行的代码咱们能够理解为是这样的dom

function fun () {
            var a = undefined
            console.log(a)
            a = 1
    }
    fun() // undefined
复制代码

这个预解析也是咱们能够在函数定义前执行function的缘由。

fun() // 1
    function fun () {
        var a = 1
        console.log(a) // 1
    }
复制代码

思考一下一个需求。让一些前面的代码在最后执行。这时小明说,很简单。把它放在最后不就好了吗😋。小明你出去🤣...

console.log('翻天')

var a = 100000
for(let i in [123, 123, 123, 123]) {
}
while (a > 0) {
    a--
}
alert(a)
alert(a)

var ajax=new XMLHttpRequest();
ajax.onreadystatechange=function(){
	console.log('ajax', ajax.responseText);
}
ajax.open("GET","https://search-merger-ms.juejin.im/v1/search?query=ajax&page=0&raw_result=false&src=web",true);
ajax.send();

// 翻天
// alert(0)
// alert(0)
// ajax, '******'
...
复制代码

能够看到最早打印翻天,而后在一行一行执行下去了,最后打印 这个异步ajax返回的结果。

那怎么办呢。咱们找到了小明,小明把cosnole.log('翻天')放到了代码的最后面。执行一下。发现竟然仍是比ajax先执行...

// 这样就行啦
    setTimeout(() => {
        console.log('翻天')
    }, 0)
    ...
    ...
    ...
    // alert(0)
    // alert(0)
    // ajax, '****'
    // 翻天
    
复制代码

你们能够在控制台执行一下。

那么为何设置一个setTimeou 0 就可让代码最后执行呢?这是由于在执行中当js碰到了setTimeout、setInterval、Promise这些东西的时候,浏览器为咱们开启了一个异步的队列。

浏览器的线程

  1. js引擎线程 (解释执行js代码、用户输入、网络请求)

  2. GUI线程 (绘制用户界面,就是咱们刚刚说的解析css和dom的,它和js主线程是互斥的)

  3. http网络请求线程 (处理ajax的)

  4. 定时触发器线程 (setTimeout、setInterval)

  5. 浏览器事件处理线程 (将click、touch放入事件队列中)

那为何setTimeout 比 ajax还后执行呢?

由于浏览器从开始执行遇到script会开启一个宏任务。遇到setTimeout也会开启一个宏任务。遇到ajax请求开启的是一个微任务。只有当一个宏任务全部的同步代码和因此微任务所有执行完毕后,浏览器才会开始下一个宏任务。这里的setTimeout 属于下一个宏任务。

难度升级,咱们再思考下面这些代码

<script>
        console.log(1)
        setTimeout(() => {
            console.log(2)
        }, 0)
        const prom = new Promise(function (ret, rej) {
            console.log(3)
            const ajax = new XMLHttpRequest();
            ajax.onreadystatechange=function(){
            	ret(4)
            }
            ajax.open("GET","https://search-merger-ms.juejin.im/v1/search?query=ajax&page=0&raw_result=false&src=web",true);
            ajax.send();
            console.log(5)
            setTimeout(() => {
                console.log(6)
            }, 0)
        })
        prom.then(res => {
            console.log(res)
            setTimeout(() => {
                console.log(7)
            })
        })
        setTimeout(() => {
            console.log(8)
        })
        console.log(9)
<script/>
复制代码

看到这段代码有没有感受日了狗🐩。思考一下,会打印啥。

// 1 3 5 9 4 2 6 8 7
复制代码

那么你的答案是对的吗?若是是,恭喜你很牛逼😀...

咱们从1到8开始讲讲为何会是这个顺序,而不是123456789.依次打印呢?
由于js引擎遇到 setTimeout 会开启一个宏任务,new Promise().then() 是一个微任务,这里的Promise是属于 script 这个宏任务的。 执行顺序是先进先出。

一个小需求

经过js事件队列实现这样一个需求

obj.eat('a') // 立刻打印'a'
obj.stop(3000).eat('a') // 间隔3秒后打印 'a'
复制代码

思考一下。


让咱们来看看一个简单的实现代码吧

class laz {
        constructor(name) {
            this.tasks = []
            setTimeout(() => {
                this.next()
            }, 0)
        }
        next () {
            let task = this.tasks.shift()
            task && task()
        }
        eat (val) {
            const task = (val => () => {
                console.log(val)
                this.next()
            })(val)
            this.tasks.push(task)
            return this
        }
        stop (time) {
            const task = (time => () => {
                setTimeout(() => {
                    console.log(time)
                    this.next()
                }, time)
            })(time)
            this.tasks.push(task)
            return this
        }
    }    
    const obj = new laz()
    obj.eat('a') // a
    obj.stop(3000).eat('a') // 三秒后 3000 a
复制代码

这个功能就是利用了js队列实现了一个简单的延迟执行.

其实这样的例子还有不少.

总结:其实一开始只是想写几百个字结束战斗...结果一写发现,就如今仍是没写全。每一个知识点都牵扯到一大堆相关联的知识点。每一个展开说均可以说半天!!
我想说的是什么呢,就是平时咱们在学习中,不要死记硬背,东西是背不完的,咱们的时间和大脑都是有限的,记住索引便可。

说实话这么一大片长文字,我本身都不想看
最后送上一句话

吾生也有涯,而知也无涯

相关文章
相关标签/搜索