JavaScript的运行机制

线程机制

进程与线程

进程(process):程序的一次执行, 它占有一片独有的内存空间,可以通过windows任务管理器查看进程

线程(thread):是进程内的一个独立执行单元,是程序执行的一个完整流程, 是CPU的最小的调度单元

应用程序必须运行在某个进程的某个线程上,一个进程中至少有一个运行的线程: 主线程,  进程启动后自动创建,一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的

一个进程内的数据可以供其中的多个线程直接共享,多个进程之间的数据是不能直接共享的

线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用

何为多进程与多线程

多进程运行: 一应用程序可以同时启动多个实例运行

多线程: 在一个进程内, 同时有多个线程运行

比较单线程与多线程

多线程:优点:能有效提升CPU的利用率,创建多线程开销;缺点:线程间切换开销,死锁与状态同步问题

单线程:优点:顺序编程简单易懂;缺点:效率低

JS是单线程还是多线程

js是单线程运行的

但使用H5中的 Web Workers可以多线程运行

浏览器运行是单线程还是多线程

都是多线程运行的

浏览器运行是单进程还是多进程

有的是单进程:FireFox,老版本的IE

有的是多进程:chrome,新版本的IE

如何查看浏览器是否是多进程运行的呢

查看任务管理器-->进程

 

 

浏览器内核

支撑浏览器运行的最核心的程序

不同的浏览器可能不一样

Chrome, Safari : webkit

firefox : Gecko

IE : Trident

360,搜狗等国内浏览器: Trident + webkit

内核由很多模块组成

js引擎模块 : 负责js程序的编译与运行(主线程)

html,css文档解析模块 : 负责页面文本的解析(主线程)

DOM/CSS模块 : 负责dom/css在内存中的相关处理 (主线程)

布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)(主线程)

定时器模块 : 负责定时器的管理(分线程)

DOM事件响应模块 : 负责事件的管理(分线程)

网络请求模块 : 负责ajax请求(分线程)

 

 

 

JS代码块

JS中的代码块是指由<script>标签分割的代码段。JS是按照代码块来进行编译和执行的,代码块间相互独立(即就算代码块1出错,但不影响代码块2的加载和执行),但变量和方法共享。

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>无标题文档</title>
    <script type="text/javascript">
        console.log("这是代码块一");
        var a = 100;
        function show() {
            console.log("show")
        }
    </script>

    <script type="text/javascript">
        show();
        console.log ("这是代码块二");
        console.log(a)
    </script>
</head>
<body>

</body>
</html>

结果可以看到第一个代码块中的方法和变量可以在第二个代码块中访问,但是需要注意的是:需要先定义在使用,因为js是同步并且单线程的,从上往下执行

 

 

 

HTML页面中JS的加载原理

在加载HTML页面的时候,当浏览器遇到内嵌的JS代码时会停止处理页面,先执行JS代码,然后再继续解析和渲染页面。

同样的情况也发生在外链的JS文件中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行它,在这个过程中,页面的渲染和用户互交完全被阻塞。

由于现代浏览器都允许并行下载JS文件,因此<script>标签在下载外部资源时不会阻塞其他的<script>标签。遗憾的是JS下载过程仍然会阻塞其他资源的下载。

 

 

JavaScript的单线程

JS语言的一大特点就是单线程,也就是说,同一个时间只能做一件事情。之所以是单线程,是因为与它的用途有关,作为浏览器脚本语言,JS的主要用途是与用户互动以及操作DOM。

这决定了它只能是单线程,否则会带来复杂的同步问题。

先来看一段代码然后再来详细的说明js的运行机制,下面的一段代码执行顺序是什么

console.log(1);
setTimeout(function () {
  console.log(2);
}, 0);
console.log(3);
console.log(4);

测试得到的结果是1,3,4,2,至于为什么会这么执行,原因就是因为js是单线程的(同一时间只能做一件事情),js的任务队列,同步任务先执行,后执行异步任务(setTimeout就是一个异步任务)

js引擎执行代码的基本流程

先执行初始化代码: 包含一些特别的代码,比如:设置定时器,绑定监听,发送ajax请求。 后面在某个时刻才会执行回调代码

setTimeout(function () {
  console.log('timeout 3') //5
}, 3000)

setTimeout(function () {
  console.log('timeout 2') // 3
  alert('2222') // 4
}, 2000)

alert('提示...') // 1, 暂停当前主线程的执行,同时暂停计时,点击确定后恢复主线程执行和计时
console.log('alert之后') //2

 

 

 

 
JavaScript的任务列队

JS任务可以分为两种:一种是同步任务,另一种是异步任务。注意,只有主线程空了,才会去读取"任务队列",这就是JS的运行机制,这个过程会不断重复。

同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕了,才会执行后一个任务。

异步任务:在主线程之外,还存在一个“任务列队”,异步任务就是不进入主线程,而是进入“任务列队”的任务,只有“任务列队”通知主线程,某个异步任务可以执行了并且同步任务执行完毕,该任务才会进入主线程执行。

 
 
 
Javascript的事件和回调函数

"任务列队"是一个事件的列队,IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。"任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。所谓"回调函数",就是那些会被主线程挂起的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。"任务队列"是个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在后文提到的"定时器"功能,主线程首先检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

 
 
 
 
定时器事件

"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。定时器功能主要由setTimeout()和setInterval()这两个函数来完成

它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeou()方法:

setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数    

console.log(1)
setTimeout(function (){
     console.log(2)
}, 1000);
console.log(3)

上面代码的执行结果是1=>3=>2,因为setTimeout()将第二行推迟到1000毫秒之后执行

如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后立即执行

console.log(1)
setTimeout(function (){
     console.log(2)
}, 0);
console.log(3)

上面代码的执行结果是1=>3=>2,因为只有在执行完第二行以后,系统才会去执行"任务队列"中的回调函数。

总之,setTimeout(fn, 0)的含义是,指定某个任务在主线程最早的空闲时间执行,也就是说尽可能早的执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事情都处理完,才会得到执行。

需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程(js是单线程的也就是说只有一个主线程)才会去执行它指定的回调函数,要是当前代码耗时很长,有可能要等很久

所以并没有办法保证回调函数一定会在setTimeout()指定的时间执行

//只打印A,B不会执行,因为while一直在执行,同步代码还没执行完,异步不会执行
console.log('A');
setTimeout(function () {
  console.log('B');
}, 0);
while (1) {

}

来看下这段程序,执行结果是打印了四次4,因为先执行主线程的循环,setTimeout属于异步任务,被挂起了,所以是for先执行完了再执行setTimeout

for (var i = 0; i < 4; i++) {
  setTimeout(function () {
    console.log(i); //打印四次4
  }, 1000);
}

  这段程序执行的结果是1,2,3,4,是因为变量i的作用域的关系

for (let i = 0; i < 4; i++) {
  setTimeout(function () {
    console.log(i); //1,2,3,4
  }, 1000);
}

 

 

 
异步任务
setTimeout和setInterval,DOM事件,ES6中的Promise
 
 
 
事件循环模型
Event Loop

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

为了更好地理解Event Loop,请看下图

stack:所有的代码都是在此空间中执行的

所有代码分类

初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码

回调执行代码(异步代码): 处理回调逻辑

js引擎执行代码的基本流程

初始化代码===>回调代码

模型的2个重要组成部分:

事件管理模块(WebApis),回调队列(callback queue)

模型的运转流程

执行初始化代码, 将事件回调函数交给对应模块管理

当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中

只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行

function fn1() {
  console.log('fn1()')
}
fn1() // 1

document.getElementById('btn').onclick = function () {
  console.log('处理点击事件') // 触发事件的时候才会执行
}

setTimeout(function () {
  console.log('到点了') //3
}, 2000)

function fn2() {
  console.log('fn2()')
}
fn2() //2

 

 

 

H5 Web Workers多线程

为了利用多核CPU的计算功能,HTML5提出了web worker标准,允许JS脚本创建多个线程,但是子线程完全受主线程控制,且不能操作DOM,所以这个新标准并没有改变JS单线程的本质。

 

posted @ 2019-05-07 22:50  胡椒粉hjf  阅读(407)  评论(0编辑  收藏  举报