JavaScript 的诞生相当有意思,应该说给 git 有点相似,都是在极短的时间内就设计诞生了,JavaScript 从设计到诞生只用了 10天的时间,不得不说它的设计者 布兰登是个天才。他的设计思路:
- 借鉴了 C 的基本语法;
- Java 的 数据类型和内存管理;
- Scheme ‘函数是第一等公民’的思想,布兰登进入网景一直跟 Scheme 打交道,最初也是像给予 Scheme 进行设计 JavaScript 的;
- 借鉴 Self 基于圆形(prototype)的继承机制;
- 借鉴 Python 语言的 字符串和数组处理方式;
综上,不难发现,JavaScript 在极短的时间内 ‘组装了起来’ ,所以感觉它既熟悉又陌生,属于函数式编程+面向对象编程,有点像东北乱炖。
它的作者,布兰登本人曾说:“与其说我爱它,不如说我恨它,它是 C 和 Self 语言一夜情的产物”。想必还有一句‘恨之越深爱之越切’。有兴趣的同学,可以先了解一下:JavaScript 的发展史
先了解几个基础概念
什么是进程?
进程属于操作系统中的概念,比如windows 系统大家都用过‘任务管理器’,mac 系统都用过“活动监视器”。打开这些工具你会发现电脑中运行了哪些应用程序,我们可以粗浅的理解这些程序就是一个‘进程’(因为一个应用程序可能不仅是一个进程,比如我们打开了chrome 浏览器,打开多个 tab 标签,你会发现这些标签页也对应到了任务管理器中的某一条)。
综上我们能发现其实进程它是需要占用操作系统的 内存存储空间和CPU 的,所以它的定义就是:当一个应用程序运行时,需要向操作系统申请使用内存和CPU资源,操作系统通过进程的方式来分配和调度这些资源(进程调度),进程的特点是独立的,一个进程不能随意访问其他进程的资源,进程与进程之间互不干扰。
什么是线程?跟进程是什么关系?
上面的那张图中,我们也会发现,每个进程中会有多个线程。跟线程紧密相关的是任务,我们可以想象一下一个进程即一个应用程序中会有很多任务要去执行,这一个个的任务就是条条线程,线程是为了完成某一个具体的任务,它的颗粒度更小。一个进程往往由一个或多个线程组成。同一个进程中的线程直接可以共享当前程序的内存空间和数据(即线程间通信)。
浏览器的运行机制
浏览器是多进程的,比如我们打开chrome 的新页签会发现,任务管理器多了一个chrome 的进程
浏览器中的进程大致可以分为如下几类:
- 主进程(Brower 进程)用于页面显示和交互控制,比如地址栏所在的那一行的控制;资源下载等;
- 浏览器渲染进程(或者说叫浏览器内核)-我们前端开发人员直接相关的就是这类;
- 第三方插件进程;
渲染进程(浏览器内核)
渲染进程(即浏览器内核)这一部分所处理的工作包括:
- 页面的渲染;
- JS 的执行;
- 事件循环
渲染进程又包含了许多线程:
- GUI渲染线程 :解析 H TML/CSS 构建 DOM 树,布局和绘制等。
- JavaScript 引擎线程 用于处理 JavaScript 脚本程序,解析和运行 js 代码。 例如 V8内核;GUI 和 JavaScript 线程互斥,两者只有一个在运行中,另一个必须等带其运行完。
- 事件触发线程:该线程用来控制事件循环,如鼠标点击事件,setTimeout ,AJAX 请求等
- 定时触发线程:计时器,setInternal 与 setTimeout 所在的线程,js 的计数不是 js 引擎支持的,而是通过这个线程来触发的。
- 异步http 请求线程 :有http 请求时浏览器会新开一条线程请求,检测返回状态回调 交由js 引擎执行。
JavaScript 为什么是单线程?
想回答这个问题,我觉得应该这么提问:为什么 JavaScript 不是多线程的呢?
首先我们要回归 js 的用途,它的用途是作为一个脚本语言实现与用户互动,以及操作DOM。这就决定了它只能是单线程,否则会带来很多复杂的同步问题。假如js 是多线程的,意味着同一时刻有两个线程在工作,一个在某个 DOM 节点上添加内容,一个在删除这个节点,这个结果应该怎么执行和展示?浏览器要怎么处理?为了避免类似这种问题,js 在最初设计的时候就是单线程的。
为了利用多核 CPU 的计算能力, H5 提出了 Web Worker 标准,允许 js 脚本创建 多个线程,但是子线程完全受主线程控制,且自线程不允许操作 DOM,所以这个标准也没有改变 js 是单线程的本质。
JavaScript 的运行机制
任务队列
我们上面了解了单线程就意味着要排队,必须要当前任务结束后才能处理下一个任务,后续任务必须要等待前一个任务执行完才能执行。
任务分类:
- 同步任务:在主线程上排队执行的任务
- 异步任务:耗时操作,在任务队列中排队的任务,它是在主线程上的同步任务执行完成后,才会执行;比如发起一个AJAX网络请求,计时器等
执行顺序可以参考下图所示:
上图中的异步任务所在的队列,我们把它叫做“任务队列”,只要主线程空了,就会去读取,这个过程会不断重复循环,这就是 JavaScript 的运行机制。
进一步分析任务队列和异步任务
任务队列的数据结构是“队列“, 队列的特点是先进先出,可以想像你在排队买票,肯定是先进入这个队列的人先买到票,买完他就出去了。IO设备 完成一项任务即触发了异步任务,就会在 任务队列中添加一个要执行的事件。
异步任务:它包含的内容可以说有如下两种分类:
- 事件(IO操作/鼠标点击/页面滚动/等等)
- 回调函数(ajax 请求,setTimout)被主线程挂起的必须等待回调的函数。