单线程的js是如何工作的
栏目: JavaScript · 发布时间: 7年前
内容简介: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工作流程(源码)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning Apache Struts
Arnold Doray / Apress / 2006-02-20 / USD 44.99
Beginning Apache Struts will provide you a working knowledge of Apache Struts 1.2. This book is ideal for you Java programmers who have some JSP familiarity, but little or no prior experience with Ser......一起来看看 《Beginning Apache Struts》 这本书的介绍吧!
html转js在线工具
html转js在线工具
HEX HSV 转换工具
HEX HSV 互换工具