【Javascript】JS的异步操作,浏览器的多线程间的协作
遇到的问题,引发了思考
今天看了一个例子,强烈引发了我对于浏览器多线程之间的操作机制、同步与异步、回调函数的兴致,代码如下:
<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进行调试,发现,代码大同小异,但是控制台输出却不一样!!
通过上面的现象,你会问,这是为什么???百思不得其解,给点耐心,往下看,涉及的知识点丰富着呢,哈哈。
这个例子可以说非常罕见,本质原因是浏览器的事件监听线程与UI渲染线程发生冲突了,到底谁先于谁执行的问题。
如果没有耐心,下面的第七点直接揭露真相。
一、前言
经过查阅了很多的资料,基本了解了JS的同步与异步的操作,浏览器内核的多线程并发操作,其中涉及到的知识点如下:
- 什么是同步和异步?
- JS的是基于事件驱动的单线程语言,为啥会有异步操作这种多线程的操作???
- 浏览器线程,浏览器内核线程间的合作?
- JS的异步操作都有哪些?它是如何工作的?
- AJAX请求的时候,浏览器内核线程机制是怎样的工作的?
- 当js主线程空闲时,此时同事发生事件触发线程和UI渲染线程,并发生冲突时,哪个会先执行问题(例如表单中文本框的输入与事件触发)
二、js单线程
单线程的含义是js只能在一个线程上运行,也就是说,同一时间只能做一件事情,其他的任务则会放在任务队列里面排队等等js线程处理。
但是值得注意的是,虽然js是单线程语言,但是并不代表浏览器内核中的js引擎线程只有一个。js引擎有多个线程,一个主线程,其他的线程配合主线程工作
三、为什么js选择单线程
与它的用途有关。作为浏览器脚本语言,Javascript主要用途是与用户互动,以及操作dom。这决定了它只能是单线程,否则会带来复杂的同步问题。比如,假设javascript同事有两个进程,一个线程在某个DOM阶段添加内容,另一个线程删除了这个节点,这是浏览器应该以哪一个线程为准?想要实现这个问题,肯定要加入线程锁这个概念,那就复杂多了。
单线程与异步确实不能同时成为一个语言的特性,JS选择成为单线程语言,所以它本身是不可能异步的,但是js的宿主环境(比如浏览器,Node)是多线程的,宿主通过某种方式,使得js具备异步的属性,如果你理解了,你会发现js的机制是多么的简单高效!
四、同步和异步
- 同步:一次只能完成一件任务,如果有多个任务,必须排队
- 异步:每一个任务有一个或者多个回调函数(callback),前一个任务执行后,把里面回调函数抛出来,给浏览器的另外一个线程处理。而后一个任务是不等待前一个任务结束就执行。
- 一个例子来说明异步和同步:
<html>
<head>
<title>异步与同步</title>
</head>
<body>
<script>
setTimeout(function () {
console.log("异步任务")
})
console.log("同步任务")
</script>
</body>
</html> //输出:
//同步任务
//异步任务 //执行顺序:
//首先执行setTimeout,发现是个异步操作,就把这个异步操作丢给浏览器内核中的
//计时器线程,然后js主线程继续执行console.log("同步任务"),之后js主线程空闲,
//从任务队列里面获取异步操作任务,放到js主线程栈中执行,输出“异步任务”
五、异步的好处
1.异步的好处
用一个同步与一个异步图进行说明,假设有四个任务(1、2、3、4),它们各自的执行时间都是10ms,其中,任务2是任务3的前置任务,任务2需要20ms的响应时间。
2.适合的场景
可以看出,当我们的程序需要大量I/O操作和用户请求时,js这个具备单线程,异步,事件驱动多种气质的语言是多么应景!相比于多线程语言,它不必耗费过多的系统开销,同时也不必把精力用于处理多线程管理,相比于同步执行的语言,宿主环境的异步和事件驱动机制又让它实现了非阻塞I/O,所以你应该知道它适合什么样的场景了吧!
六、JS的异步操作有哪些
我们已经知道,js一直是单线程执行的,浏览器为了几个明显的耗时任务(例如ajax),单独开辟线程解决耗时问题。但是JS除了这几个明显耗时问题外,可能我们自己写的程序里面也会有耗时函数,怎么解决?我们肯定不能直接开辟单独的线程,但是我们可以利用浏览器给我们开发的窗口(定时器线程和事件触发线程),总的来说,有一下4种异步操作:
- 定时器函数setTimeout(交给定时器线程处理)
- 事件绑定(onclick、onkeydown....,交给事件触发线程处理)
- AJAX(交给HTTP线程处理)
- 回调函数
七、说说浏览器的线程
前面说了,js是单线程,浏览器只分配给JS一个主线程,用来执行函数,但一次只能执行一个任务,假设有多个任务,就会形成一个任务队列,意味着这些任务需要排队等待执行,但前端某些任务是非常耗时的,例如网络请求、定时器和事件监听,如果让他们和别的任务一样,老老实实排队等待执行的话,执行效率会非常低,甚至可能导致页面假死。
所以为了解决这个问题,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务都是异步的。依靠宿主(浏览器、Node)的多线程,来使自己具备异步的属性。
浏览器的内核是多线程,它们在内核控制下相互配合以保持同步,一个浏览器至少实现3个常驻线程
- Javascript引擎线程(浏览器无论什么时候都只有一个JS线程在运行)
- UI渲染线程(与Javascript引擎线程互斥,Javascript引擎线程执行时,UI渲染线程被挂起,当Javascript引擎空闲时,UI渲染线程立即执行)
- 事件触发线程(当一个事件被触发时该线程会把事件添加到待处理队列的对位,等待JS引擎处理。这些事件可来自Javascript引擎当前执行的代码块,如setTimeout,也可以来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等。)
下图可以说明浏览器的三个主要线程
从图片可以看出,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。
八、最后说说js主线程
js除了处理自己的主线程的任务之外,还在背地里一直做一个工作,就是从任务队列(callback queue)中提取任务,放到主线程里执行,下图深入讲解了JS主线程的工作:
上图的WebAPIs,可以统一理解为浏览器为异步任务单独开辟的线程
上图的callback queue,就是上面所说的任务队列,里面放的就是回调函数体
JS主线程由堆(heap)与栈(stack)共同组成。函数的执行通过进栈出栈来执行,例如foo()函数,主线程把它推进栈,在执行它的过程中,发现还需要执行上面几个函数,所以JS主线程又陆陆续续把几个函数推进栈中,等到函数执行完,把他们一一推出栈,等到stack清空的时候,说明一个任务已经执行完成了,这时就会从callback queue中寻找下一个任务,把他推进栈中(这个寻找过程,就叫event loop,因为它总是循环查找任务队列里是否还有任务)
九、说说一些应用与实践吧
AJAX发送异步请求,浏览器做了什么?
1.JS创建了一个ajax请求
2.浏览器另外打开一个ajax引擎线程,执行ajax请求
3.执行的到响应后将回调函数体放入任务队列
4.js线程执行任务队列中的回调函数
参考内容:
https://www.cnblogs.com/woodyblog/p/6061671.html (博客主要参考)
https://zhuanlan.zhihu.com/p/23659122?refer=dreawer
https://blog.csdn.net/w2765006513/article/details/53743051
https://blog.csdn.net/baidu_24024601/article/details/51861792
https://www.bilibili.com/video/av18833649?from=search&seid=15859434788305704049 (视频参考)
https://www.cnblogs.com/yelongsan/p/6296700.html
--END--
【Javascript】JS的异步操作,浏览器的多线程间的协作的更多相关文章
- JS 监听浏览器各个标签间的切换
以前看到过一些网页,在标签切换到其它地址时,网页上的标题上会发生变化,一直不知道这个是怎么做的,最近查了一些资料才发现有一个 visibilitychange 事件就可以搞定,这里将介绍一下页面可见性 ...
- [HTML/Javascript] JS判断IE浏览器各版本
代码参考: function isLowIEVersion() { var browser = navigator.appName var b_version = navigator.appVersi ...
- C#多线程的用法3-线程间的协作Join
在创建多线程应用程序时,如何确保线程间的协作往往比让线程工作更重要. 线程间的协作最简单的方式是采用Join来进行,如下: /// <summary> /// 多线程协作-Join方式 / ...
- 多进程浏览器、多线程页面渲染与js的单线程
线程与进程 说到单线程,就得从操作系统进程开始说起.在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位.任务调度采用的是时间片轮转的抢占式调度方式,而进程 ...
- 浏览器UI多线程及JavaScript单线程运行机制的理解
在上一篇博客中,我对jQuery的队列(queue)机制和动画(animate)机制做了一个深入的解析,在animate的实现机制其核心是依靠queue来完成的,其中在jQuery的链式调用部分,之前 ...
- TraceGL监控Node.js应用或者浏览器JavaScript代码
https://github.com/traceglMPL/tracegl TraceGL能够监控Node.js应用或者浏览器JavaScript代码的运行过程和细节.可视化的用户界面也很友好
- JavaScript js 兼容浏览器问题 兼容Fire
做BS开发就难免会用到javascript,而每个浏览器对javascript的支持有不同.这就需要我们程序员去兼容他们,不然有些浏览器就无法运行我们的代码.就会造来客户的投诉,如果让BoSS知道了, ...
- 浏览器的多线程和js的单线程--前端易混淆知识科普(一)
问题:js是单线程的,页面是从上往下加载的,那么是不是第一个js没加载完成,第二个js就不加载?然后,引申出来一个问题就是,那css和图片呢?这之间的加载有相互影响吗? 1.什么是线程?什么是进程?什 ...
- 【MarkMark学习笔记学习笔记】javascript/js 学习笔记
1.0, 概述.JavaScript是ECMAScript的实现之一 2.0,在HTML中使用JavaScript. 2.1 3.0,基本概念 3.1,ECMAScript中的一切(变量,函数名,操作 ...
随机推荐
- BZOJ_1367_[Baltic2004]sequence_结论题+可并堆
BZOJ_1367_[Baltic2004]sequence_结论题+可并堆 Description Input Output 一个整数R Sample Input 7 9 4 8 20 14 15 ...
- 【SAP HANA】新建账户和数据库(2)
开启HANA Studio,进入到User和Role的目录,这两个地方是创建账号和权限的. 新建用户 输入用户名和密码即可. 注意,如果系统里有同名的Catalog(数据库)存在的话,会报错,因为默认 ...
- djiango的模板语言(template)
老师的博客:http://www.cnblogs.com/liwenzhou/p/7931828.html 官方文档:https://docs.djangoproject.com/en/1.11/re ...
- 【转】W3C中国与百度联合组织移动网页加速技术研讨会
2017 年 8 月 30 日,W3C 会员百度在北京中关村软件园国际会议中心主办了 "移动网页加速技术研讨会",W3C 中国以及腾讯.阿里巴巴及 UC.搜狗.小米.傲游.中国移动 ...
- CART决策树和随机森林
CART 分裂规则 将现有节点的数据分裂成两个子集,计算每个子集的gini index 子集的Gini index: \(gini_{child}=\sum_{i=1}^K p_{ti} \sum_{ ...
- ssm框架搭建和整合流程
Spring + SpringMVC + Mybatis整合流程 1 需求 1.1 客户列表查询 1.2 根据客户姓名模糊查询 2 整合思路 第一步:整合dao层 ...
- Spring Boot 中实现定时任务的两种方式
在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Qua ...
- 我为什么放弃MySQL?最终选择了MongoDB
最近有个项目的功能模块,为了处理方便,需要操作集合类型的数据以及其他原因.考虑再三最终决定放弃使用MySQL,而选择MongoDB. 两个数据库,大家应该都不陌生.他们最大的区别就是MySQL为关系型 ...
- 第四节 pandas 数据加载
pandas提供了一些用于将表格型数据读取为DataFrame对象的函数,其中read_csv和read_table这两个使用最多. #导包import pandas as pd from panda ...
- 泛微oa几个常用的js
泛微OA,常用JS 为满足一些简单需求,我从网上借鉴了大量的代码,其中几个是非常好用的. (1).取值判断 通过jQuery('#field1234').val()取字段的值,field1234对应字 ...