JS引擎:它们是如何工作的?从调用堆栈到Promise,需要知道的所有内容

论坛 期权论坛 期权     
筑梦编程   2019-7-20 10:27   4060   0
有没有想过浏览器如何读取和运行JS代码?这看起来很神奇,我们可以通过浏览器提供的控制台来了解背后的一些原理。

在Chrome中打开浏览器控制台,然后查看Sources这栏,在右侧可以到一个Call Stack 盒子。







考虑以下代码:
  1. var num = 2;function pow(num) {    return num * num;}
复制代码
如果问你如何在浏览器中处理上述代码? 你会说些什么?你可能会说“浏览器读取代码”或“浏览器执行代码”。

现实比这更微妙。首先,读取这段代码的不是浏览器,是JS引擎。JS引擎读取代码,一旦遇到第一行,就会将几个引用放入全局内存

全局内存(也称为堆) JS引擎保存变量和函数声明的地方。因此,回到上面示例,当 JS引擎读取上面的代码时,全局内存中放入了两个绑定。






回到我们的例子,当函数被调用时,JS引擎将该函数推入调用堆栈


同时,JS 引擎还分配了一个全局执行上下文,这是运行JS代码的全局环境,如下所示


想象全局执行上下文是一个海洋,其中全局函数像鱼一样游动,多美好!但现实远非那么简单, 如果我函数有一些嵌套变量或一个或多个内部函数怎么办?

即使是像下面这样的简单变化,JS引擎也会创建一个本地执行上下文:
  1. var num = 2;function pow(num) {    var fixed = 89;    return num * num;}pow(num);
复制代码
注意,我在
  1. pow
复制代码
函数中添加了一个名为
  1. fixed
复制代码
的变量。在这种情况下,pow函数中会创建一个本地执行上下文,
  1. fixed
复制代码
变量被放入
  1. pow
复制代码
函数中的本地执行上下文中。

对于嵌套函数的每个嵌套函数,引擎都会创建更多的本地执行上下文。
[h1]JavaScript 是单线程和其他有趣的故事[/h1]



  1. setTimeout(callback, 10000);function callback(){    console.log('hello timer!');}
复制代码
  1. setTimeout
复制代码
大家都知道得用得很多次了,但你可能不知道它不是内置的JS函数。也就是说,当JS 出现,语言中没有内置的
  1. setTimeout
复制代码

  1. setTimeout
复制代码
浏览器API( Browser API)的一部分,它是浏览器免费提供给我们的一组方便的工具。这在实战中意味着什么?由于
  1. setTimeout
复制代码
是一个浏览器的一个Api,函数由浏览器直接运行(它会在调用堆栈中出现一会儿,但会立即删除)。

10秒后,浏览器接受我们传入的回调函数并将其移动到回调队列(Callback Queu)中。。考虑以下代码
  1. var num = 2;function pow(num) {    return num * num;}pow(num);setTimeout(callback, 10000);function callback(){    console.log('hello timer!');}
复制代码
示意图如下:

如你所见,
  1. setTimeout
复制代码
在浏览器上下文中运行。10秒后,计时器被触发,回调函数准备运行。但首先它必须通过回调队列(Callback Queue)。回调队列是一个队列数据结构,回调队列是一个有序的函数队列。

每个异步函数在被放入调用堆栈之前必须通过回调队列,但这个工作是谁做的呢,那就是事件循环(Event Loop)。

事件循环只有一个任务:它检查调用堆栈是否为空。如果回调队列中(Callback Queue)有某个函数,并且调用堆栈是空闲的,那么就将其放入调用堆栈中。

完成后,执行该函数。以下是用于处理异步和同步代码的JS引擎的图:



想象一下,
  1. callback()
复制代码
已准备好执行,当
  1. pow()
复制代码
完成时,调用堆栈(Call Stack) 为空,事件循环(Event Look) 将
  1. callback()
复制代码
放入调用堆中。大概就是这样,如果你理解了上面的插图,那么你就可以理解所有的JavaScript了。
[h1]回调地狱和 ES6 中的Promises[/h1]JS 中回调函数无处不在,它们用于同步和异步代码。考虑如下
  1. map
复制代码
方法:
  1. function mapper(element){    return element * 2;}[1, 2, 3, 4, 5].map(mapper);
复制代码
  1. mapper
复制代码
是一个在
  1. map
复制代码
内部传递的回调函数。上面的代码是同步的,考虑异步的情况:
  1. function runMeEvery(){    console.log('Ran!');}setInterval(runMeEvery, 5000);
复制代码



  1. const myPromise = new Promise(function(resolve){    setTimeout(function(){        resolve()    }, 5000)});
复制代码
如下所示,resolve是一个函数,调用它是为了使Promise 成功,别外也可以使用
  1. reject
复制代码
来表示调用失败。
  1. const myPromise = new Promise(function(resolve, reject){    setTimeout(function(){        reject()    }, 5000)});
复制代码
注意,在第一个示例中可以省略
  1. reject
复制代码
,因为它是第二个参数。但是,如果打算使用
  1. reject
复制代码
,则不能忽略
  1. resolve
复制代码
,如下所示,最终将得到一个
  1. resolved
复制代码
的承诺,而非
  1. reject
复制代码
  1. // 不能忽略 resolve !const myPromise = new Promise(function(reject){    setTimeout(function(){        reject()    }, 5000)});
复制代码



将会输出:
  1. Catching the error! Error: Sorry mate!
复制代码
现在尝试使用异步函数:
  1. function makeAnError() {  throw Error("Sorry mate!");}try {  setTimeout(makeAnError, 5000);} catch (error) {  console.log("Catching the error! " + error);
复制代码
由于
  1. setTimeout
复制代码
,上面的代码是异步的,看看运行会发生什么:
  1.   throw Error("Sorry mate!");  ^Error: Sorry mate!    at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9)
复制代码
这次的输出是不同的。错误没有通过
  1. catch
复制代码
块,它可以自由地在堆栈中向上传播。
那是因为
  1. try/catch
复制代码
仅适用于同步代码。如果你很好奇,Node.js中的错误处理会详细解释这个问题。

幸运的是,Promise 有一种处理异步错误的方法,就像它们是同步的一样:
  1. const myPromise = new Promise(function(resolve, reject) {  reject('Errored, sorry!');});
复制代码
  1. [/code]
  2. [code]
复制代码
在上面的例子中,我们可以使用
  1. catch
复制代码
处理程序处理错误:
  1. const myPromise = new Promise(function(resolve, reject) {  reject('Errored, sorry!');});myPromise.catch(err => console.log(err));
复制代码






那如何处理错误?
  1. async/await
复制代码
提一个好处就是可以使用
  1. try/catch
复制代码
。再看一下Promise,我们使用
  1. catch
复制代码
处理程序来处理错误:
  1. const myPromise = new Promise(function(resolve, reject) {  reject('Errored, sorry!');});myPromise.catch(err => console.log(err));
复制代码
  1. [/code]使用[code]async
复制代码
函数,我们可以重构以上代码:
  1. async function getData() {  try {    const data = await myPromise;    console.log(data);    // or return the data with return data  } catch (error) {    console.log(error);  }}getData();
复制代码
并不是每个人都喜欢这种风格。
  1. try/catch
复制代码
会使代码变得冗长,在使用
  1. try/catch
复制代码
时,还有另一个怪异的地方需要指出,如下所示:
  1. async function getData() {  try {    if (true) {      throw Error("Catch me if you can");    }  } catch (err) {    console.log(err.message);  }}getData()  .then(() => console.log("I will run no matter what!"))  .catch(() => console.log("Catching err"));
复制代码
运行结果:

以上两个字符串都会打印。请记住,try/catch 是一个同步构造,但我们的异步函数产生一个Promise。 他们在两条不同的轨道上行驶,比如两列火车。但他们永远不会见面, 也就是说,throw 抛出的错误永远不会触发**getData()**的
  1. catch
复制代码
方法。

实战中,我们不希望
  1. throw
复制代码
  1. then
复制代码
的处理程序。一种的解决方案是从函数返回
  1. Promise.reject()
复制代码
  1. async function getData() {  try {    if (true) {      return Promise.reject("Catch me if you can");    }  } catch (err) {    console.log(err.message);  }}
复制代码
  1. [/code]现在按预期处理错误
  2. [code]getData()  .then(() => console.log("I will NOT run no matter what!"))  .catch(() => console.log("Catching err"));"Catching err" // 输出
复制代码
[code][/code]



*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。



公众号ID:tzbc666

有趣的灵魂在等你长按扫码可关注

点个好看和转发也是一种支持哟!
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:75
帖子:15
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP