2024-03-11 深入理解JavaScript运行时机制:事

2024-03-10  本文已影响0人  我是小胡胡分胡

问题1

原生模块还可以使用 promise 来简化代码,搭配 ES2016(ES7)标准的async/await语法则效果更佳。如果桥接原生方法的最后两个参数是RCTPromiseResolveBlock和RCTPromiseRejectBlock,则对应的 JS 方法就会返回一个 Promise 对象。

我们把上面的代码用 promise 来代替回调进行重构:

RCT_REMAP_METHOD(findEvents,
                 findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
  NSArray *events = ...
  if (events) {
    resolve(events);
  } else {
    NSError *error = ...
    reject(@"no_events", @"There were no events", error);
  }
}

现在 JavaScript 端的方法会返回一个 Promise。这样你就可以在一个声明了async的异步函数内使用await关键字来调用,并等待其结果返回。(虽然这样写着看起来像同步操作,但实际仍然是异步的,并不会阻塞执行来等待)。

async function updateEvents() {
  try {
    const events = await CalendarManager.findEvents();

    this.setState({ events });
  } catch (e) {
    console.error(e);
  }
}

updateEvents(); 

怎么样理解这里说的:这样你就可以在一个声明了async的异步函数内使用await关键字来调用,并等待其结果返回。(虽然这样写着看起来像同步操作,但实际仍然是异步的,并不会阻塞执行来等待)。

回答
这段说明是在讲述使用 Promise 和 async/await 来处理异步操作时的一种编码风格和思想。

首先,异步操作的处理方式有两种:回调函数和 Promise。回调函数是传统的方式,但它容易导致回调地狱(callback hell),代码不易读,不易维护。Promise 则是一种更为现代化的处理方式,它可以链式调用,更易于理解和组织。

而 async/await 则是在 Promise 的基础上进一步封装的语法糖,使得异步代码看起来更像同步代码,更易读。使用 async/await,你可以在异步函数内部使用 await 关键字来等待 Promise 的结果,而不需要使用 .then() 方法处理回调。这样的代码看起来更加清晰和简洁。

在你提供的代码示例中,通过将原生模块的回调方法改为 Promise 形式,可以在 JavaScript 端的异步函数内部使用 await 关键字来等待原生方法的结果,这样做看起来就像同步操作一样,但实际上并不会阻塞执行。这是因为使用了 async/await 语法,使得 JavaScript 运行时可以在等待异步操作的同时继续执行其他任务,直到异步操作完成并返回结果。

总之,这段说明是在鼓励使用 Promise 和 async/await 来处理异步操作,以提高代码的可读性和可维护性。

问题2

User
当程序执行到 const events = await CalendarManager.findEvents();这一行的时候,会阻塞吗。如果外面调用updateEvents方法也用了await

回答
当程序执行到 const events = await CalendarManager.findEvents(); 这一行时,会发生暂停,但不会阻塞整个程序。JavaScript 运行时会暂停执行 updateEvents() 函数,并等待 CalendarManager.findEvents() 方法的 Promise 对象被解决(即异步操作完成)。

如果外部调用 updateEvents() 方法也使用了 await 来等待其执行结果,那么当外部调用的代码执行到这个 await updateEvents(); 时,也会发生暂停,直到 updateEvents() 函数的执行完毕并返回结果。

这种行为是 async/await 语法的特点之一,它允许在异步代码中使用类似同步的语法结构来编写程序,但实际上并不会阻塞整个程序的执行。JavaScript 运行时会在异步操作等待的同时继续执行其他任务,直到异步操作完成并返回结果,然后再继续执行后续的代码。

问题3

User
如何理解“暂停” 和“阻塞”

回答
理解“暂停”和“阻塞”的概念对于理解异步编程非常重要。

暂停(Pausing):在异步操作中,当程序执行到一个异步操作(比如一个 await 表达式)时,该操作可能需要等待某些资源(比如网络请求、文件读取等)完成,然后才能继续执行后续的代码。在这种情况下,当前函数的执行会暂停,JavaScript 引擎会转而执行其他可执行的任务。当异步操作完成后,JavaScript 引擎会回到原来的函数,并继续执行后续的代码。
阻塞(Blocking):阻塞是指当一个任务执行时,它会阻止其他任务的执行。在同步代码中,如果一个任务需要等待某些操作完成,那么它会一直等待直到操作完成,期间不会执行其他任务。这样的行为被称为阻塞,因为它阻止了程序的其他部分继续执行。
在 JavaScript 中,由于其单线程的特性,通常情况下是不会发生真正的阻塞的,因为 JavaScript 运行时会采用事件循环机制来处理异步任务,使得在等待异步操作完成的同时,可以执行其他任务,从而避免了阻塞整个程序的执行。相比之下,“暂停”更适用于描述异步操作等待的情况,而“阻塞”更适用于描述同步操作的行为。

问题4

User
运行时会采用事件循环机制来处理异步任务,使得在等待异步操作完成的同时,可以执行其他任务,从而避免了阻塞整个程序的执行。可以举一个例子说明吗,在等待异步操作完成的同时,在等待异步操作完成的同时

回答
当在 JavaScript 中执行异步操作时,事件循环机制确保其他任务可以被执行,而不会阻塞程序的执行。这意味着在等待异步操作完成的同时,JavaScript 运行时可以处理其他任务,比如响应用户输入、执行其他函数等。

举个简单的例子,假设我们有一个异步函数 fetchData() 用于从服务器获取数据,同时我们也想要在获取数据的过程中更新界面以提示用户正在加载数据。在这种情况下,我们可以使用事件循环机制来实现:

javascript
Copy code

async function fetchData() {
    // 模拟一个异步操作,比如向服务器请求数据
    await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟等待2秒钟

    return "Data from server";
}

async function updateUI() {
    console.log("Updating UI...");
    // 更新界面,显示加载数据的提示
    document.getElementById("loading-message").innerText = "Loading data...";

    // 在等待异步操作完成的同时,JavaScript 运行时可以处理其他任务
    const data = await fetchData();

    // 当异步操作完成后,更新界面,显示获取到的数据
    console.log("Data received:", data);
    document.getElementById("loading-message").innerText = ""; // 清除加载数据的提示
    document.getElementById("data").innerText = data;
}

// 调用 updateUI 函数
updateUI();

console.log("Other tasks..."); // 这行代码会立即执行,不会等待异步操作

在这个例子中,当调用 updateUI() 函数时,它会立即开始执行。在 updateUI() 函数中,首先会更新界面以显示加载数据的提示,然后调用 fetchData() 函数来获取数据。虽然 fetchData() 函数是异步的,并且需要等待2秒钟才能完成,但是在等待的过程中,JavaScript 运行时仍然可以执行其他任务,比如输出 "Other tasks...",这是因为事件循环机制的作用。当数据获取完成后,再更新界面以显示获取到的数据。

问题5:

User
console.log("Other tasks..."); 这段代码执行的所在线程 和return "Data from server"; 这行代码执行的县城 已经等待2秒钟的这个线程,是相同的线程吗

回答
在 JavaScript 中,单线程是指所有 JavaScript 代码都在同一个线程中执行。这个线程被称为主线程或者称为 UI 线程,它负责执行 JavaScript 代码、处理用户交互、渲染界面等任务。

在你提供的例子中,console.log("Other tasks..."); 这段代码和 return "Data from server"; 这行代码都是在主线程中执行的,因为 JavaScript 代码都运行在同一个线程中。在 fetchData() 函数中,await new Promise(resolve => setTimeout(resolve, 2000)); 这行代码是在主线程中执行的,它创建了一个定时器,并在2秒钟后将异步操作(Promise)的状态设置为已完成。

尽管定时器的回调函数会在2秒钟后执行,但这并不会阻塞主线程的执行,因为 JavaScript 运行时会继续处理其他任务。当定时器到期后,它的回调函数会被添加到事件队列中,等待主线程处理。一旦主线程空闲,事件循环会检查事件队列,将定时器的回调函数推入调用栈中执行,从而完成异步操作。

所以,在等待2秒钟的过程中,JavaScript 主线程会继续执行其他任务,比如执行 console.log("Other tasks...");。这两行代码都是在同一个线程中执行的,即 JavaScript 主线程。

问题6

User
这里提到了一个事件队列的概念, 等待2秒这个底层是怎么实现的,是谁一直在监听这个倒计时

回答
在 JavaScript 运行时中,事件队列是用来管理异步操作的。当一个异步操作完成时,它会将其对应的回调函数添加到事件队列中,等待 JavaScript 主线程处理。

具体来说,当调用 setTimeout 函数时,JavaScript 运行时会创建一个定时器,并将定时器的回调函数添加到事件队列中。同时,定时器会开始计时,当计时器时间到期后,定时器的回调函数会被添加到事件队列中。JavaScript 主线程会不断地从事件队列中取出事件,并执行对应的回调函数,这就是事件循环机制的核心。

在等待2秒钟的过程中,底层的实现依赖于操作系统提供的定时器机制。在浏览器中,通常是使用浏览器提供的定时器 API,比如 setTimeout 和 setInterval。浏览器会使用底层的系统 API 来管理定时器,比如 setTimeout 可能会调用操作系统提供的定时器功能,比如 setTimeout 的实现可能会调用 setTimeout 函数并注册一个回调函数,告诉操作系统在一定时间后执行这个回调函数。操作系统会负责在指定时间到达后通知浏览器,然后浏览器会将定时器的回调函数添加到事件队列中,等待 JavaScript 主线程处理。

总的来说,底层的实现是通过操作系统提供的定时器机制来实现的,而 JavaScript 运行时会监听这些定时器,并在适当的时候将定时器的回调函数添加到事件队列中。

问题7

User
javascript运行时机制是什么样的

回答
JavaScript 运行时的机制可以被描述为事件驱动和单线程的。这个机制主要包括以下几个组成部分:

事件循环(Event Loop):JavaScript 运行时使用事件循环来处理异步操作。事件循环负责监听事件队列,当事件队列中有事件时,事件循环会将事件取出并调用相应的回调函数。事件循环会持续运行,不断地监听事件队列,执行对应的任务。
任务队列(Task Queue):任务队列存储着待处理的任务或事件。在 JavaScript 中,任务队列通常包括宏任务队列(Macrotask Queue)和微任务队列(Microtask Queue)。宏任务队列用于存储一些较大的任务,比如 setTimeout、setInterval、I/O 操作等。微任务队列用于存储一些较小的任务,比如 Promise 的回调函数、MutationObserver 的回调等。事件循环会首先处理微任务队列中的所有任务,然后再处理宏任务队列中的任务。
调用栈(Call Stack):调用栈用来存储当前执行的上下文(函数调用栈)。当一个函数被调用时,它的执行上下文会被压入调用栈中,当函数执行完成后,它的执行上下文会被弹出调用栈。调用栈的特性决定了 JavaScript 是单线程的,即一次只能执行一个任务,一次只能有一个函数在调用栈中执行。
Web API:Web API 是浏览器提供的一组 API,用于处理一些异步操作,比如定时器、网络请求、DOM 事件等。Web API 可以将异步操作的回调函数添加到事件队列中,等待 JavaScript 主线程处理。
综合起来,JavaScript 运行时通过事件循环、任务队列和调用栈等机制来实现异步编程。当有异步操作时,JavaScript 运行时会将异步操作的回调函数添加到事件队列中,等待 JavaScript 主线程处理。主线程在空闲时会不断地从事件队列中取出事件,并执行相应的回调函数,从而完成异步操作。这种机制保证了 JavaScript 是单线程的,同时能够处理异步任务,使得 JavaScript 能够处理复杂的并发操作。

问题8

User
既然JavaScript 是单线程的,javascript运行时的事件循环,也只是处理耗时任务的回调函数和存储弹出调用栈的相关操作,那么真正复杂耗时的任务是谁在处理,哪个线程在处理

回答
在 JavaScript 中,虽然主线程是单线程的,但是浏览器中的一些底层操作,比如网络请求、DOM 操作、定时器等,是由浏览器的其他线程来处理的,而不是 JavaScript 主线程。

具体来说,浏览器通常会有多个线程来处理不同的任务,比如:

GUI 渲染线程:负责渲染页面,处理 DOM 树的构建和绘制,以及处理用户交互事件,比如点击、滚动等。这个线程与 JavaScript 主线程是分开的。
网络线程:负责处理网络请求和响应,包括发送请求、接收响应、解析响应等操作。这个线程通常是由浏览器底层的网络模块来管理的,与 JavaScript 主线程是分开的。
定时器线程:负责处理定时器的计时和触发,当定时器到期时,会将定时器的回调函数添加到事件队列中,等待 JavaScript 主线程处理。
Web Worker 线程:Web Worker 是一种特殊的 JavaScript 线程,它可以在后台运行,独立于主线程,用于处理一些耗时的计算任务,比如大量的数据处理、复杂的算法等。Web Worker 与主线程之间通过消息传递来通信,可以在 Web Worker 中执行一些复杂耗时的任务,而不会阻塞主线程。
总的来说,在 JavaScript 运行时中,虽然主线程是单线程的,但浏览器会利用多线程来处理一些复杂的耗时任务,从而提高页面的响应速度和用户体验。JavaScript 主线程通过事件循环和任务队列机制来管理异步操作,而其他线程则负责处理一些底层的操作,比如网络请求、DOM 渲染等。

上一篇下一篇

猜你喜欢

热点阅读