JS在浏览器的执行机制
- 主线程和执行栈,所有的任务都会放到执行栈中等待主线程来执行
- 任务队列:承载任务的队列。Event Loop就是会不断地过来循环访问这个队列,查看是否有任务可以运行
-
执行过程:
- 主线程自上而下执行所有代码
- 同步任务直接进入到主线程被执行,而异步任务则进入到Event Table并注册相对应的回调函数
- 异步任务完成后,Event Table会将这个函数移入任务队列
- 主线程任务执行完了以后,会从任务队列中读取任务,进入到主线程去执行
- 循环以上
- 浏览器端事件循环中的异步队列有两种:宏任务队列和微任务队列
- 宏任务(MacroTask):几乎所有的同步代码都是宏任务,还包括setTimeOut等
- 微任务(MicroTask):prmise.then(new Promise不是微任务)
JS的执行上下文
分为全局执行上下文和函数执行上下文:
- 全局执行上下文:任何不在函数内部的代码都在全局上下文中。浏览器的情况下,它会执行两件事:创建一个全局的 window对象,并且设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文
- 函数执行上下文:函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行
- 执行栈:后进先出,用来存储代码运行时创建的所有执行上下文
- 作用域:作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。JavaScript采用词法作用域,也就是静态作用域
- 词法作用域:函数的作用域在函数定义的时候就决定了
- 闭包:闭包是一个可以访问外部作用域中变量的内部函数,这些被引用的变量直到闭包被销毁时才会被销毁,可以通过闭包来达到封装性
-
函数结束时:
- 这个本地执行上下文从执行栈中弹出
- 函数将返回值返回调用上下文
- 这个本地执行上下文被销毁,这个本地执行上下文中声明的所有变量都将被删除,不再有变量
-
this指向:
- 由new调用:绑定到新创建的对象
- 由call/apply/bind调用:绑定到指定的对象
- 由上下文对象调用:绑定到上下文对象
- 默认:全局对象
- 箭头函数根据外层作用域来决定this,继承外层函数调用的this绑定
浏览器进程
- 浏览器(多进程)包含了主进程、第三方插件进程和GPU进程(浏览器渲染进程),其中GPU进程(多线程)和Web前端密切相关,包含以下线程
- GUI渲染线程
- JS引擎线程
- 事件触发线程(和Event Loop密切相关)
- 定时触发器线程
-
异步HTTP请求线程</br>
注:GUI渲染线程和JS引擎线程是互斥的,为了防止DOM渲染的不一致性,其中一个线程执行时另一个线程会被挂起
-
任务处理:
- 宏任务是每次执行栈执行的代码
- 浏览器为了能够使得JS引擎线程与GUI渲染线程有序切换,会在当前宏任务结束之后,下一个宏任务执行开始之前,对页面进行重新渲染(宏任务 > 渲染 > 宏任务 > ...)
- 微任务是在当前宏任务执行结束之后立即执行的任务。微任务的响应速度相比setTimeout(下一个宏任务)会更快,因为无需等待UI渲染
- 当前宏任务执行后,会将在它执行期间产生的所有微任务都执行一遍
- 宏任务中的事件是由事件触发线程来维护的
- 微任务中的所有任务是由JS引擎线程维护的
-
任务处理流程:
- 执行一个宏任务
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有任务(依次执行)
- JS引擎线程挂起,GUI线程执行渲染
- GUI线程渲染完毕后挂起,JS引擎线程执行任务队列中的下一个宏任务