js为什么是单线程的?
假如我们有一个dom需要操作,如果一个函数是让这个dom的宽度变大,而另一个是宽度变小,那么浏览器该执行哪一个操作呢?或者更为极端的情况,一个是修改dom,另一个是删除这个dom,那么浏览器又该如何反应呢?
综上所述,js执行一定是单线程的,而不是并发执行命令。
但是不能并发的话,像淘宝主页如此多的图片加载、数据请求,排着队等待页面加载完成的的话,估计都能打完一盘斗地主了。但实际上这种大型网站的加载时十分迅速的,让我们感觉请求、渲染似乎是在同时执行的,那么JS又做了什么来让我们以为一切都是”并发“的呢?
这个答案就是:异步。
异步和并发有什么区别?
为了之后深入了解异步与并发,首先先来回顾一下操作系统的基本概念:
什么是进程?什么是线程?
每个工厂都有独立的资源
工厂之间相互独立
线程是工厂中的工人,多个工人协作完成任务
工厂内有一个或多个工人
工人之间共享空间
如果工厂=进程,工人=进程,那么上面的话应该翻译成这样:
- 每个进程有独立的资源
- 进程之间相互独立
- 一个进程可以包含一个或多个线程,线程相互协作完成任务
- 同一进程下各个线程共享资源(内存空间)
如果我们打开Mac的任务管理器,可以很清楚的看到线程与进程的关系,同时,我们也可以发现进程与CPU的关系: 进程是CPU资源分配的最小单位。
进程是cpu资源分配的最小单位,是能独立拥有资源和独立运行的最小单位。
线程是cpu调度和分派任务的最小单位。
有关于进程和线程的概念,可以参考阮一峰老师的进程与线程的一个简单解释。现在明白了,每开一个应用,就会启动一个进程,进程又会启动一个或多个线程执行命令。
请等一下,浏览器不是也有个任务管理器吗,让我们看看浏览器的任务管理器长什么样。
我们发现,每一个任务(tab页)都会有CPU资源和内存分给它,并且每个任务都有其进程ID。现在我们明白了,并不是一个浏览器对应一个进程,而是一个Tab页对应一个进程,对于Chrome而言,浏览器是多进程的。(建议英文好的同学读一读原文,链接:Inside look at modern web browser,本文顶部的参考资料也列出了翻译地址)。同时我们还发现,chrome还将域名相同的tab页做了合并,共享内存空间(这一步并没有查到相关的资料,chrome是通过怎样的机制合并进程的,如果有了解的同学可以私信我)。
PS:这里如果深挖的话,可以了解以下浏览器的各个线程,例如GUI渲染线程、JS引擎线程等等,就先留个坑慢慢填吧。
什么是异步?什么是并发?
有了以上的基础,我们知道线程是可以并发的(一个进程多个线程嘛),而一个线程里的任务只能一个一个按序执行。我们从线程进程来看,可以对异步与同步做出以下的总结:
『同步』执行,其实是没有“任务队列”的『异步』操作。
如何实现js的异步
- callback
- Promise
- 生成器
- Async与await
- setTimeout *
关于setTimeout,这里需要说明的是,其实它的延时操作并不是真正的延时。因为setTimeout的本质并不是延迟多少毫秒执行,而是延迟多少毫秒将回调函数放入任务队列中。如果此时你的任务队列排满了待执行的任务,那么延时的回调会按照顺序往后排。这时如果又不断有promise请求在“插队”的话,setTimeout的回调会一直延后。关于这点,大家可以参考微任务、宏任务与Event-Loop,就能有很清晰的认识了。
这篇文章是基于js是如何影响浏览器渲染的这个角度出发的,因为同时有多个任务对DOM渲染进行操作的话,是很容易出现问题的。但我们知道,App是多线程的,那它又是怎样保证UI渲染的稳定性呢?
扩展:APP的渲染机制
安卓的UI线程(Main Thread)
当一个安卓应用启动的时候,系统会同时启动一个对应的主线程(Main Thread),这个线程的主要任务就是处理与UI相关的事件,同时,系统对每一个组件的调用都是从该主线程分发出去的。
因此,安卓系统规定:只能在UI线程对UI进行处理,同时禁止将网络操作、数据库操作放在主线程中执行。
iOS的UI线程(Main Thread)
和安卓一样,在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程,由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面。
参考资料: