单线程的js是如何工作的
栏目: JavaScript · 发布时间: 5年前
内容简介:js在诞生初期就确定了其在讨论单线程的js时,我们先来看看为什么js要是单线程的。让js变成多线程的不行么 ~~有个叫 Web Worker 的东西,可以让浏览器开出一个Worker 线程,通过接受发送消息和主线程交互,并且和主线程不冲突的执行。web worker的定位是负责需要大量计算的代码执行线程。它限制了我们不能访问 DOM 对象。
js在诞生初期就确定了其 单线程 的定位,也就是说,所有 任务需要排队 ,前一个行行执行,前面的执行完,才会执行后面的。如果前一个任务耗时很长,后一个任务就不得不一直等着。
在讨论单线程的js时,我们先来看看为什么js要是单线程的。让js变成多线程的不行么 ~~
为什么js是单线程
- js诞生初只是为了实现一些简单的表单验证,也就没必要太强大。也许一个项目也就几十行js代码。那为啥现在js越来越庞大,浏览器厂商不给改改呢?让我们来看第二点。
- 作为一个脚本语言,JavaScript的 主要用途是与用户互动 ,比如操作DOM,那么问题来了,如果有JavaScript有两个线程,这里两个线程同时改变一个div的背景色,那浏览器听谁的,好像两个线程都没问题...
- 虽然js是线程的,但 浏览器是多线程 的
有个叫 Web Worker 的东西,可以让浏览器开出一个Worker 线程,通过接受发送消息和主线程交互,并且和主线程不冲突的执行。web worker的定位是负责需要大量计算的代码执行线程。它限制了我们不能访问 DOM 对象。
写了半天怎么能没有代码呢..
var a = 0 while (a < 10000000) { a++ } console.log(10000000) console.log(123) // 123 // 321 复制代码
看上面这个代码,先执行while 循环,在打印 a,最后123,这很好理解,js就是单线程,一行一行解释执行。就算while 要加一个亿,后面的代码也得等着
事件监听
那我们的代码是不是就没办法异步执行了呢?当然不是。我们的代码需要承担一个非常重要的任务就是,完成和用户的交互。比如你点击一个按钮希望它能给你弹出一个弹窗。
// 来看个很简单的例子 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的。这就是 事件监听 通过事件监听我们可以实现,一个异步任务。
写着写着感觉还是需要简单的说一个ur从输入框到加载完成的主要流程。
- 用户abcd输入一个url,然后浏览器发起DNS查询请求(dns就是把abcd和真实的ip地址1234 对应起来的服务器)
- 找到了1234,浏览器就向1234发起建立TCP连接(tcp是一些列协议,其中http是它应用层上的一个,感兴趣的同学可以搜索一下 TCP七层模型 ,也有叫五层的)
- 发送HTTP 请求(http携带报文给服务器,请求行、请求头、请求体)
- 浏览器解析http返回的东西(我们就说一个html吧)
- 浏览器解析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预解析了一次,实际执行的代码我们可以理解为是这样的
function fun () { var a = undefined console.log(a) a = 1 } fun() // undefined 复制代码
这个预解析也是我们可以 在函数定义前执行function 的原因。
fun() // 1 function fun () { var a = 1 console.log(a) // 1 } 复制代码
思考一下一个需求。让一些前面的代码在最后执行。这时小明说,很简单。把它放在最后不就行了吗:yum:。小明你出去 ...
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这些东西的时候,浏览器为我们开启了一个异步的队列。
浏览器的线程
-
js引擎线程 (解释执行js代码、用户输入、网络请求)
-
GUI线程 (绘制用户界面,就是我们刚刚说的解析css和dom的,它和js主线程是互斥的)
-
http网络请求线程 (处理ajax的)
-
定时触发器线程 (setTimeout、setInterval)
-
浏览器事件处理线程 (将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/> 复制代码
看到这段代码有没有感觉日了狗:poodle:。思考一下,会打印啥。
// 1 3 5 9 4 2 6 8 7 复制代码
那么你的答案是对的吗?如果是,恭喜你很牛逼:grinning:...
我们从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队列实现了一个简单的延迟执行.
其实这样的例子还有很多.
总结:其实一开始只是想写几百个字结束战斗...结果一写发现,就现在还是没写全。每个知识点都牵扯到一大堆相关联的知识点。每个展开说都可以说半天!!
我想说的是什么呢,就是平时我们在学习中,不要死记硬背,东西是背不完的,我们的时间和大脑都是有限的,记住索引即可。
说实话这么一大片长文字,我自己都不想看
最后送上一句话
吾生也有涯,而知也无涯
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【重回基础】线程池源码剖析:Worker工作线程
- node.js 12.11.0 发布,工作线程(多线程)稳定
- 【译】Node.js工作线程介绍
- 漫画 Java 线程池的工作机制
- 漫画 Java 线程池的工作机制
- Android多线程-AsyncTask工作流程(源码)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java编程思想 (第4版)
[美] Bruce Eckel / 陈昊鹏 / 机械工业出版社 / 2007-6 / 108.00元
本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作。本书的作者拥有多年教学经验,对C、C++以及Java语言都有独到......一起来看看 《Java编程思想 (第4版)》 这本书的介绍吧!