欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入理解 JavaScript 代码执行机制

程序员文章站 2024-02-09 10:37:46
...

深入理解 JavaScript 代码执行机制

前言

本文仅为个人见解,如有错误的地方欢迎留言区探讨和指正。

1、餐前甜品

如下是一段 JavaScript 代码,如果你毫不犹豫的说出代码执行顺序。那么请直接滚动到底部,留下你的足迹,接受膜拜。如果还不是很确定,那么请往下继续查看。

let element = document.querySelector('.test');
    num = 0,
    start = null;

function step(timestamp) {
  console.log('requestAnimationFrame');
  if (!start) start = timestamp;
  var progress = timestamp - start;

  const p = new Promise((resolve, reject) => {
    resolve('then');
  }).then(() => {
    console.log('promise1111');
  });

  element.style.left = Math.min(progress / 10, 200) + 'px';
  if (progress < 3000) {
    window.requestAnimationFrame(step);
  }
}

document.body.onscroll = function(ev) {
  console.log('我被滚动了');
}

window.requestAnimationFrame(step);

// 试试将 16改成18,在看看打印的代码顺序
setTimeout(() => {
  console.log('setInterval');
}, 16); 

const p = new Promise((resolve, reject) => {
  console.log('promise');
  resolve('then');
}).then(() => {
  console.log('then');
});

2、磨刀不误砍柴工(了解浏览器原理)

(1) 进程和线程

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一个程序运行单位,一个进程中可以有多个线程)

进程可以类比为工厂,线程就是工厂里面的工人,一个工厂可以包含一个或者多个工人,工人之间可以相互协作,并且共享工作空间

(2) 浏览器的多进程架构

现代的浏览器采用的都是多进程架构,主要包含以下三种进程:

1.Browser进程
浏览器的主线程,主要负责浏览器的页面管理、书签、前进后退、资源下载管理等,整个浏览器应用程序只有一个,对应上述浏览器组成中的浏览器引擎。

2.渲染进程
内核进程、负责页面渲染、JS执行,对应的是上述的渲染引擎和JS引擎,一个浏览器可以包含多个渲染进程,每个Tab窗口页对应一个渲染进程

3.GPU进程
负责GPU渲染,整个浏览器应用程序只有一个

4.插件进程
浏览器安装的插件(扩展程序),每个插件会创建一个进程

这种多进程浏览器架构主要有如下优势:

  • 1.避免单个页面奔溃影响整个浏览器
  • 2.避免第三方插件奔溃影响整个浏览器
  • 3.充分利用多核优势

(3) 浏览器的渲染进程

  • 浏览器有多个渲染进程、一个Tab页面一个(相同的Tab页面可能会被合并)
  • 一个渲染进程包含多个线程

一个渲染进程主要包括如下线程:

  1. GUI线程(主要负责解析HTML、CSS和渲染页面),也就是人们常说的渲染引擎
  2. JS引擎线程(负责解析和执行JS代码)
  3. 事件线程(控制事件循环)
  4. 定时器线程(处理定时器相关逻辑)
  5. 异步请求线程(发起Ajax时会生成该线程)

线程规则:

  1. GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,页面的更新操作会等到JS引擎空闲时执行
  2. 一个渲染进程同时只有一个JS解析线程在运行
  3. JS引擎线程不停的处理事件线程推送到事件队列中的任务
  4. 定时器和异步请求最终生成的回调事件也有事件线程来控制和管理

常见浏览器的渲染引擎和JS引擎如下:

浏览器 渲染引擎 JS引擎
IE Trident Chakra
Edge EdgeHTML Chakra
Firefox Gecko SpiderMonkey
Chrome Webkit -> Blink V8(著名的)
Safri Webkit Javascriptcore
Opera Presto->Blink Carakan

了解了浏览器的渲染进程之后我们再来看看JS引擎。

3、正餐(JS引擎)

javascript 是单线程执行,且是 **逐帧 **执行。这个时候有人就要抬杠了,说webWorker 不是可以支持多线程么?没错,webworker 是可以开启子线程,但是此线程并不能操作dom。

js 代码执行分同步任务和异步任务,异步任务在当前同步任务队列执行完成后在依次执行。在 ES6中,又将异步任务分为了宏任务(macrotask)和微任务(microtask)。

ps: node 环境的任务不在此文讨论

(1) 宏任务

分类:

  • setTimeout
  • setInterval
  • I/O
  • UI交互事件
  • postMessage
  • MessageChannel

(2) 微任务

分类:

  • Promise.then
  • MutationObserver

(3) event-loop (任务事件执行顺序)

js 主线程先执行同步任务,遇见微任务和宏任务就将其交给事件线程处理,进行堆栈。当当前同步任务执行完成后,会读取当前的微任务队列。如果有,则依次执行。如果没有,则寻找是否有异步队列。如果有,则执行当前异步任务。如此反复,直至任务完成。

图解:
深入理解 JavaScript 代码执行机制

(4) requestAnimationFrame 是属于宏任务还是微任务?

其实,requestAnimationFrame 既不属于宏任务也不属于微任务。那么它在何时执行呢?答案就是:浏览器会在下一帧开始的时候把它加入到 微任务(microtask) 队列的最前方。

答案解析

深入理解 JavaScript 代码执行机制
所以上方的代码的代码解析就是 js 代码在每一帧的执行,永远是同步任务执行,然后在执行异步操作的时候,先清空微任务队列,然后在根据浏览器策略来判断 requestAnimationFrame 在哪一帧开始执行,最后清空宏任务队列。

最后,有兴趣的童鞋可以将上方的代码拷贝到本地,将 setTimeout 的数值改一下,然后会出现不一样的现象。有什么疑问欢迎在评论去留言,也欢迎吐槽。