我们知道浏览器中javascript程序的执行是基于变量与函数的。那么浏览器是如何保存数据,又是如何执行的呢?今天我们一起来探究一下!

0.写在前

最新的 ECMAScript 标准定义了 8 种数据类型:7种原始类型:Undefined、Null、Boolean、Number、BigInt、String、Symbol(新增)和一种复杂类型对象Object(也叫引用类型);

浏览器内存空间分为栈(stack)堆(heap)池(一般也会归类为栈中)。 其中存放变量,存放复杂对象,存放常量。

1. javascript执行

1.1 在程序解析的最初阶段(预解析)

通过function定义的函数被解析器声明且定义,作为一种特殊的对象,函数的函数体会被保存在堆内存中,函数名会保存在栈内存中,其值为函数体的堆内存地址

使用var声明的变量进行变量提升,浏览器会将这些变量保存到栈中,变量默认为undefined。在这个阶段函数声明比变量声明优先。

1.2 程序执行阶段(逐行执行)

变量声明完成之后,代码开始执行。在此阶段变量被赋值,原始类型值被直接保存到栈内存中。引用类型值保存在堆内存中,并将堆内存中的引用地址赋值给栈内的变量。

随后一些函数开始被执行。首先浏览器通过函数名从堆内存中把函数体捞起来,然后开始解析、运行。我们通常使用执行上下文这么一个抽象的概念来表示JavaScript被解析和运行时的环境,JavaScript 运行任何代码都是在执行上下文环境中运行的。
函数被调用,解析但是还没有开始执行时的状态称为预解析。首先函数的执行上下文初始化arguments对象,提升函数声明和变量声明,然后创建作用域链,当前作用域没有定义该变量时,便会向上查找,取值。如果最终没有就为undefined。这种层层之间就构成了作用域链。最后会确定this指向。
当函数执行时会在内存形成了一个调用帧,随着函数的运行函数内部还调用了其他函数又会形成一个调用帧,所有的调用帧就形成一个调用栈又称为执行栈。可以把执行栈认为成一个储存函数调用的栈结构,遵循先进后出的原则。函数执行完成后,执行上下文从底部退出,等待垃圾回收。

1.3垃圾回收阶段

当函数体被执行后,JavaScript 中无用的变量会被回收,这个过程是由javascript的内存管理是自动执行的.
JavaScript 中内存管理的主要概念是可达性。简单地说,“可达性” 值就是那些以某种方式可访问或可用的值,它们被保证存储在内存中。

有一组基本的固有可达值,由于显而易见的原因无法删除。例如本地函数的局部变量和参数,当前嵌套调用链上的其他函数的变量和参数,全局变量,还有一些其他的,内部的函数及变量。这些值称为根。

如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。
JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。

垃圾回收内部算法

基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:
垃圾回收器获取根并“标记”(记住)它们。
然后它访问并“标记”所有来自它们的引用。
然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
以此类推,直到有未访问的引用(可以从根访问)为止。
除标记的对象外,所有对象都被删除。

闭包

函数执行时产生的新的变量与函数会作为局部变量同样被保存在了内存中,若当一函数的返回值引用了局部变量,同时这个返回结果一直被其他变量引用,那么会导致函数内部的变量不会被正常的回收,形成闭包。

2. 消息队列与Event Loop

在程序执行时,我们经常讨论的一个问题就是消息队列与Event Loop(事件循环)。

一个 JavaScript 运行时包含了一个待处理的消息队列,它由处理异步任务的结果的回调函数组成。异步任务回调不进入主线程,当异步任务产生结果时其回调函数会被塞进”任务队列”(task queue)。异步任务任务包括UI事件、ajax网络请求、定时器setTimeout和setInterval等。
所以消息队列中的每一个消息都与一个函数(回调函数callback)相关联。当调用栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了结果与回调函数(以及因而创建了一个初始堆栈帧)。当栈再次为空的时候,也就意味着消息处理结束。这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

更简单的说:只要主线程空了(同步),就会去读取”任务队列”(异步),这就是JavaScript的运行机制。

2.1宏队列和微队列

  1. 宏队列,macrotask(tasks)。 当异步结果返回时异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:
  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)
  1. 微队列,microtask(jobs)。 当异步结果返回时异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
  • process.nextTick (Node独有)
  • Promise
  • Object.observe
  • MutationObserver

(注:这里只针对浏览器和NodeJS)

2.2 Event Loop的执行

执行一个JavaScript代码的具体流程:

  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
  2. 全局Script代码执行完毕后,调用栈Stack会清空;
  3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
  4. 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行
  5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
  6. 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
  7. 执行完毕后,调用栈Stack为空;
  8. 重复第3-7个步骤;
重点:
  1. 宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
  2. 微任务队列中所有的任务都会被依次取出来执行,直到microtask queue为空;
  3. UI rendering的节点是由浏览器自行判断决定的。执行UI rendering的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。

简述javascript的解析与执行的更多相关文章

  1. 第112天:javascript中函数预解析和执行阶段

    关于javascript中的函数:  1.预解析:把所有的函数定义提前,所有的变量声明提前,变量的赋值不提前  2.执行 :从上到下执行,但有例外(setTimeout,setInterval,aja ...

  2. javascript的加载、解析、执行对浏览器渲染的影响

    javascript的加载方式,总得来说是在页面上使用script来声明,以及动态的加载这些方式,而动态的加载,在很多js库中都能够很好的去处 理,从而不至于阻塞其他资源的加载,并与其并行加载下来.这 ...

  3. 简述JavaScript模块化编程(二)

    前置阅读:简述JavaScript模块化(一) 在前面一文中,我们对前端模块化所经历的三个阶段进行了了解: CommonJs,由于是同步的,所以主要应用于服务器端,以Node.js为代表. AMD,异 ...

  4. 关于JavaScript预编译和执行顺序以及函数引用类型的思考

    昨晚在对项目中的一部分做模块化处理的时候,遇到了一个问题,一个重新定义的function对一个通用类中的function进行赋值覆盖的时候,失败了.问题抽象出来是这样的: <script > ...

  5. JS的解析与执行过程

    JS的解析与执行过程 全局中的解析和执行过程 预处理:创建一个词法环境(LexicalEnvironment,在后面简写为LE),扫描JS中的用声明的方式声明的函数,用var定义的变量并将它们加到预处 ...

  6. [转]Javascript中的自执行函数表达式

    [转]Javascript中的自执行函数表达式 本文转载自:http://www.ghugo.com/javascript-auto-run-function/ 以下是正文: Posted on 20 ...

  7. 深入理解javascript中的立即执行函数(function(){…})()

    投稿:junjie 字体:[增加 减小] 类型:转载 时间:2014-06-12 我要评论 这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是 ...

  8. javascript预解析和作用域

    JavaScript解析过程分为两个阶段: 一是:编译阶段.就是JavaScrip预解析阶段,在这个阶段JavaScript解析器将完成把JavaScript脚本代码转换到字节码; 二是:执行阶段.在 ...

  9. Javascript this 解析

    Javascript中,this是一个非常有用的关键字, this是在运行时基于函数的运行环境绑定的,但是,如果使用的时候不注意,很容易就出错了. ECMAScript Standard对this的定 ...

随机推荐

  1. 测试环境docker-swarm安装部署

    测试环境swarm安装部署 部署前增加监听docker2375端口 centos 增加tcp监听端口 修改/lib/systemd/system/docker.service sed -i ‘s/Ex ...

  2. linux理论知识点(用于考试)

    ps:为其十天左右的linux培训即将结束了,未雨绸缪,为了更好的通过之后的考试,提前多看些考试题和知识点.这是在chinaunix论坛看到的一个帖子,贴来分享. 原文地址:[http://bbs.c ...

  3. Spring Boot 2.x 缓存应用 Redis注解与非注解方式入门教程

    Redis 在 Spring Boot 2.x 中相比 1.5.x 版本,有一些改变.redis 默认链接池,1.5.x 使用了 jedis,而2.x 使用了 lettuce Redis 接入 Spr ...

  4. bootstrap与vue,react的区别

    链接(与Vue区别):https://www.php.cn/faq/423095.html 链接(BootStrap, React, Vue的比较):https://www.jianshu.com/p ...

  5. ETCD授权认证

    export ETCDCTL_API=3 ENDPOINTS=localhost:2379 etcdctl --endpoints=${ENDPOINTS} role add root etcdctl ...

  6. docker环境下mysql数据库的备份

    #! /bin/bash DATE=`date +%Y%m%d%H%M%S` BACK_DATA=erp-${DATE}.sql #导出表结构,不包括表数据 #docker exec -i xin-m ...

  7. WLC-WLC升级(以2504为例)

    1.WLC升级需要按照升级路径来操作,低版本到高版本的跨度太大,往往需要升级到中间版本,有时候还涉及到FUS. 2.我们升级,一般使用的笔记本上运行的TFTP/FTP  server. 需要注意:笔记 ...

  8. spring boot 配置时区差别

    前提 数据库时区:GMT+8 show variables like '%time_zone%'; 本机电脑时区: 情景一.不指定时区 传递的参数映射到Data不指定时区,连接数据库不指定时区,保存时 ...

  9. lc 0224

    目录 ✅ 766. 托普利茨矩阵 描述 解答 cpp py ✅ 566. 重塑矩阵 描述 解答 java py ✅ 637. 二叉树的层平均值 描述 解答 cpp py java 0224 algo ...

  10. 树莓派4B踩坑指南 - (12)谷歌浏览器书签同步

    书签和插件不能同步真的是不方便..使用时删掉※符号 过程比较复杂,坑很多,但确认有效 免费访问说明: https://github.com/max2max/fre※es※s 软件安装 https:// ...