遇到的问题,引起了思考javascript
今天看了一个例子,强烈引起了我对于浏览器多线程之间的操做机制、同步与异步、回调函数的兴致,代码以下:html
<html> <head> <title>title</title> </head> <body> <input type="text" value="" name="input" onkeydown="console.log(this.value)"> <input type="text" value="" name="input" onkeydown="var me = this;setTimeout(function(){console.log(me.value)});"> </body> </html>
若是有兴趣,你能够直接运行这段代码,打开网页,用F12进行调试,发现,代码大同小异,可是控制台输出却不同!!前端
经过上面的现象,你会问,这是为何???百思不得其解,给点耐心,往下看,涉及的知识点丰富着呢,哈哈。java
这个例子能够说很是罕见,本质缘由是浏览器的事件监听线程与UI渲染线程发生冲突了,到底谁先于谁执行的问题。ajax
若是没有耐心,下面的第七点直接揭露真相。浏览器
1、前言网络
通过查阅了不少的资料,基本了解了JS的同步与异步的操做,浏览器内核的多线程并发操做,其中涉及到的知识点以下:多线程
2、js单线程并发
单线程的含义是js只能在一个线程上运行,也就是说,同一时间只能作一件事情,其余的任务则会放在任务队列里面排队等等js线程处理。dom
可是值得注意的是,虽然js是单线程语言,可是并不表明浏览器内核中的js引擎线程只有一个。js引擎有多个线程,一个主线程,其余的线程配合主线程工做
3、为何js选择单线程
与它的用途有关。做为浏览器脚本语言,Javascript主要用途是与用户互动,以及操做dom。这决定了它只能是单线程,不然会带来复杂的同步问题。好比,假设javascript同事有两个进程,一个线程在某个DOM阶段添加内容,另外一个线程删除了这个节点,这是浏览器应该以哪个线程为准?想要实现这个问题,确定要加入线程锁这个概念,那就复杂多了。
单线程与异步确实不能同时成为一个语言的特性,JS选择成为单线程语言,因此它自己是不可能异步的,可是js的宿主环境(好比浏览器,Node)是多线程的,宿主经过某种方式,使得js具有异步的属性,若是你理解了,你会发现js的机制是多么的简单高效!
4、同步和异步
<html>
<head>
<title>异步与同步</title>
</head>
<body>
<script>
setTimeout(function () {
console.log("异步任务")
})
console.log("同步任务")
</script>
</body>
</html>
//输出:
//同步任务
//异步任务
//执行顺序:
//首先执行setTimeout,发现是个异步操做,就把这个异步操做丢给浏览器内核中的
//计时器线程,而后js主线程继续执行console.log("同步任务"),以后js主线程空闲,
//从任务队列里面获取异步操做任务,放到js主线程栈中执行,输出“异步任务”
5、异步的好处
1.异步的好处
用一个同步与一个异步图进行说明,假设有四个任务(一、二、三、4),它们各自的执行时间都是10ms,其中,任务2是任务3的前置任务,任务2须要20ms的响应时间。
2.适合的场景
能够看出,当咱们的程序须要大量I/O操做和用户请求时,js这个具有单线程,异步,事件驱动多种气质的语言是多么应景!相比于多线程语言,它没必要耗费过多的系统开销,同时也没必要把精力用于处理多线程管理,相比于同步执行的语言,宿主环境的异步和事件驱动机制又让它实现了非阻塞I/O,因此你应该知道它适合什么样的场景了吧!
6、JS的异步操做有哪些
咱们已经知道,js一直是单线程执行的,浏览器为了几个明显的耗时任务(例如ajax),单独开辟线程解决耗时问题。可是JS除了这几个明显耗时问题外,可能咱们本身写的程序里面也会有耗时函数,怎么解决?咱们确定不能直接开辟单独的线程,可是咱们能够利用浏览器给咱们开发的窗口(定时器线程和事件触发线程),总的来讲,有一下4种异步操做:
7、说说浏览器的线程
前面说了,js是单线程,浏览器只分配给JS一个主线程,用来执行函数,但一次只能执行一个任务,假设有多个任务,就会造成一个任务队列,意味着这些任务须要排队等待执行,但前端某些任务是很是耗时的,例如网络请求、定时器和事件监听,若是让他们和别的任务同样,老老实实排队等待执行的话,执行效率会很是低,甚至可能致使页面假死。
因此为了解决这个问题,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务都是异步的。依靠宿主(浏览器、Node)的多线程,来使本身具有异步的属性。
浏览器的内核是多线程,它们在内核控制下相互配合以保持同步,一个浏览器至少实现3个常驻线程
下图能够说明浏览器的三个主要线程
从图片能够看出,JS引擎线程是单线程,它从上到下执行任务,当执行到鼠标点击事件、setTimeout这些异步任务的话,它会交给浏览器的对应的线程执行,事件触发线程就会把回调函数插入到Javascript引擎线程的队尾,等待js线程执行。这个过程当中,由于Javascript线程一直繁忙,因此UI渲染线程一直被挂起。
------------------------------------------------------------------------------------------------------------------------------------------------------
说到这里,基本上能够解决本文开篇所说的问题了。
第一个文本框的js是这样写的:
<input type="text" value="" name="input" onkeydown="console.log(this.value)">
一开始按下键盘a的时候,已经触发了事件,onkeydown为异步操做,它的函数体会交给事件监听进程,此时JS线程空闲,你觉得UI线程会启动?事实上并不会,UI线程依然被挂起,缘由是事件监听进程比UI线程更快执行(当事件触发线程与ui渲染线程发生冲突时,例如例子中的表单的输入与触发),事件监听进程把函数体插入到任务队列尾部,JS引擎执行就经过event loop机制,把这段函数体压进栈中执行。因JS引擎线程繁忙,因此UI渲染线程方面,a字符串无法渲染到dom里,因此console.log在得到文本框的值时,为”“(空)。以后js线程空闲,UI渲染线程执行,页面渲染出a出来。
第二个文本框的js是这样写的:
<input type="text" value="" name="input" onkeydown="var me = this;setTimeout(function(){console.log(me.value)},0);">
一开始按下键盘a的时候,触发了事件,js执行里面的函数,发现setTimeout是异步操做时,虽然它的延迟设为0,几乎是便是触发的,可是把函数丢给浏览器setTimeout定时器线程处理,此时js线程空闲,UI渲染线程把a渲染到页面,以后事件触发线程把回调函数插入js线程待处理的任务队列,js线程执行回调函数,最后浏览器及时输出字符串a。
8、最后说说js主线程
js除了处理本身的主线程的任务以外,还在背地里一直作一个工做,就是从任务队列(callback queue)中提取任务,放到主线程里执行,下图深刻讲解了JS主线程的工做:
上图的WebAPIs,能够统一理解为浏览器为异步任务单独开辟的线程
上图的callback queue,就是上面所说的任务队列,里面放的就是回调函数体
JS主线程由堆(heap)与栈(stack)共同组成。函数的执行经过进栈出栈来执行,例如foo()函数,主线程把它推动栈,在执行它的过程当中,发现还须要执行上面几个函数,因此JS主线程又陆陆续续把几个函数推动栈中,等到函数执行完,把他们一一推出栈,等到stack清空的时候,说明一个任务已经执行完成了,这时就会从callback queue中寻找下一个任务,把他推动栈中(这个寻找过程,就叫event loop,由于它老是循环查找任务队列里是否还有任务)
9、说说一些应用与实践吧
AJAX发送异步请求,浏览器作了什么?
1.JS建立了一个ajax请求
2.浏览器另外打开一个ajax引擎线程,执行ajax请求
3.执行的到响应后将回调函数体放入任务队列
4.js线程执行任务队列中的回调函数
参考内容:
http://www.javashuo.com/article/p-pozgohqt-bd.html (博客主要参考)
https://zhuanlan.zhihu.com/p/23659122?refer=dreawer
http://www.javashuo.com/article/p-oxsktebw-gn.html
https://blog.csdn.net/baidu_24024601/article/details/51861792
https://www.bilibili.com/video/av18833649?from=search&seid=15859434788305704049 (视频参考)
http://www.javashuo.com/article/p-xaannlnq-z.html
--END--