js代码执行过程、调用栈、执行上下文

news2024/11/18 3:21:22

参考资料

极客时间课程《浏览器工作原理与实践》 – 李兵

一、js代码执行过程

(一)javascript代码的执行流程

浏览器执行javascript代码的流程如下图所示:

js执行流程

javascript的执行机制是:先编译,再执行。在编译阶段生成了执行上下文、可执行代码。执行上下文由变量环境、词法环境构成,它为可执行代码在执行时提供运行环境。

在编译阶段,javascript引擎将变量的声明部分和函数的声明部分提升到代码开头,并且给被提升的变量设置默认值:undefined,同时这些被提升的变量将被放入到变量环境对象中。

(二)变量提升(Hoisting)

所谓变量提升就是js代码在编译阶段,js引擎将变量的声明部分和函数的声明部分提升到代码开头的一种行为。在js代码执行前会先进行编译,在编译阶段出现了变量提升,关于编译阶段为什么会出现变量提升、变量提升带来的问题、ES6如何修正变量提升,将在 变量提升 中进行深入了解。下面先对变量提升做初步了解:
变量提升

下面举例分析一下:

  1. 例1:常见的变量提升
//示例代码
console.log("fun1", fun1);
showName()
console.log("myname",myname);
var myname = "aaa";
function showName () {
    console.log("执行showName函数");
}
var fun1 = function () {
	console.log("另一种函数声明方式");
}

//变量提升:
function showName () {
	console.log("执行showName函数")
}
var myname = undefined;
var fun1 = undefined;

//可执行代码:
console.log("fun1", fun1);
showName()
console.log("myname",myname);
myname = "aaa";
fun1 = function () {
	console.log("另一种函数声明方式");
}

//执行结果:
fun1	undefined
执行showName函数
myname	undefined

该例子中的变量环境对象大致如下表所示,对于函数声明(function声明),函数体一起被提升,并存在变量环境对象中。当函数被调用时,函数体会被编译,并创建函数执行上下文,为函数运行提供环境。当函数执行完毕,函数执行上下文将被销毁。

变量名
showNamefunction () { console.log(“执行showName函数”); }
mynameundefined
fun1undefined
  1. 例2:变量与函数同名
//示例代码

showName()
/** 函数表达式等同变量声明处理,函数声明在前,变量声明在后 **/
function showName () {
	console.log(1);
}
/** 重新赋值 **/
var showName = function () {
	console.log(2);
}
showName()

//变量提升:
/** 函数提升优先级高于变量提升,先被提升 **/
function showName () {
    console.log(1);
}
var showName = undefined;

//可执行代码:
showName()
showName = function () {
	console.log(2);
}
showName()

//执行结果:
1
2

分析:变量提示时,存在变量showName与函数showName同名,此时:函数提升比变量提升的优先级要高,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。简单来说就是:在进行变量提升时,先提升函数声明,再提升变量声明;当有变量名与函数名相同时,函数声明不会被变量声明覆盖,但在运行阶段,可以通过赋值语句,对该变量进行重新赋值。所以上述代码运行结果与下面代码运行结果一致:

//示例代码
showName()
/** 函数表达式等同变量声明处理,变量声明在前,函数声明在后 **/
/** 重新赋值 **/
var showName = function () {
    console.log(2);
}
function showName () {
    console.log(1);
}
showName()

//变量提升:
/** 函数提升优先级高于变量提升,先被提升 **/
function showName () {
    console.log(1);
}
var showName = undefined;

//可执行代码:
showName()
showName = function () {
	console.log(2);
}
showName()

//执行结果:
1
2
  1. 例3:函数与函数同名,js编译阶段会选择最后声明的那个

总结:变量提升只提升声明,不提升赋值,针对变量声明与函数声明进行提升。函数声明主要有:函数声明、函数表达式。其中函数声明会将方法体也提升,而函数表达式同变量提升一样,只会提升声明。当变量提升遇到ES6的let/const,会出现暂时性死区,效果就像没有提升。另外要注意在ES6的块级作用域中,var变量能够穿透块提升到全局。

二、调用栈

栈是一种后进先出的数据结构。在执行js代码时,可能会存在多个执行上下文,这些执行上下文可能是编译全局代码创建的全局执行上下文(全局唯一)、编译函数体创建的函数执行上下文、编译eval函数创建的执行上下文。这些执行上下文之间也可能存在相互调用关系,js引擎采用栈来对这些执行上下文进行管理。通常将这种用于管理执行上下文的栈称为调用栈,也叫做执行上下文栈。

调用栈

通过代码示例分析调用栈如何管理执行上下文:

调用栈管理上下文

  1. 创建全局执行上下文,生成可执行代码,并将上下文压入栈底
    a. 更新全局上下文,a = 2;
    b. 调用addAll

  2. 开始编译addAll函数体,生成可执行代码,并将上下文入栈
    a. 更新函数上下文,d = 10;
    b. 调用add

  3. 开始编译add函数体,生成可执行代码,并将上下文入栈
    a. 执行函数返回:return b + c ; // 9

  4. add函数上下文被弹出栈,更新addAll函数上下文:result = 9;

  5. addAll函数继续执行
    a. 执行函数返回:return a + result + d; // 21

  6. add函数上下文被弹出栈,无需更新全局上下文

  7. 全局上下文继续执行

  8. 执行完毕

总结:js引擎通过执行上下文的变量环境来支持变量提升。

三、变量提升

在深入了解变量提升前,需要先了解一下什么是作用域,以及ES6出现的块级作用域。

(一)作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。简单来说,作用域就是变量与函数的可访问范围,它控制着变量和函数的可见性和生命周期,决定了变量在何处能够被访问,在何时会被销毁。在ES6之前,js只有全局作用域和函数作用域,在ES6时为了解决变量提升带来的一系列问题,引入了块级作用域。以下是在各个作用域中定义的变量的访问范围和生命周期:

  • 全局作用域:
    • 访问范围:代码中的任何地方
    • 生命周期:伴随页面的生命周期
  • 函数作用域:
    • 访问范围:在函数内部
    • 生命周期:在函数执行时存在,执行结束后被销毁
  • 块级作用域:
    • 访问范围:在{}代码块内部
    • 生命周期:在执行{}代码块时存在,执行结束后被销毁

作用域

(二)变量提升带来的问题

  1. 变量容易在不被察觉的情况下被覆盖掉
/** 示例代码 **/
var myname = "1";
function showName () {
    console.log(myname);
    if (0) {
        var myname = "2";
    }
    console.log(myname);
}
showName()


/** 全局执行上下文 **/
//变量提升
function showName () {
    console.log(myname);
    if (0) {
        var myname = "2";
    }
    console.log(myname);
}
var myname = undefined;
//可执行代码
myname = "1";
showName()


/** 函数执行上下文 **/
//变量提升
var myname = undefined;
//可执行代码
console.log(myname);
if (0) {
    myname = "2";
}
console.log(myname);


/** 输出结果 **/
undefined
undefined

分析:该代码在ES6出现前,调用函数showName时开始编译showName函数体,if条件中声明的myname由于没有块级作用域而被提升至函数顶部,并放入函数执行上下文中的变量环境对象。在执行可执行代码时,js优先从当前的执行上下文中查找变量。由于当前执行上下文中包含了变量myname,值为undefined,所以输出结果均为undefined。该段代码在有块级作用域的其他语言中,输出结果应当均为"1"。

  1. 本应被销毁的变量没有被销毁
/** 示例代码 **/
function foo () {
    for (var i = 0; i < 7; i++) {
        
    }
    console.log(i);
}
foo()


/** 全局执行上下文 **/
//变量提升
function foo () {
    for (var i = 0; i < 7; i++) {}
    console.log(i);
}
//可执行代码
foo()


/** 函数执行上下文 **/
//变量提升
var i = undefined
//可执行代码
for (i = 0; i < 7; i++) {}
console.log(i);


/** 输出结果 **/
7

分析:在创建函数执行上下文时,变量i被提升,当for循环结束后,变量i并没有被销毁,它存在函数执行上下文的变量环境对象中。这导致输出结果为7。而在有块级作用域的其他语言中,for循环完毕,变量应当被销毁。

(三)ES6如何修正变量提升

在最初设计js语言时,设计者没有打算把这门语言设计得太复杂,只是引入函数作用域和全局作用域,忽略了一些块级作用域。这样如果变量或者函数在if块、while块里面,因为它们没有作用域,所以在编译阶段,可以直接把这些变量或者函数提升到开头,这样大大降低了设计语言的复杂性,但也埋下了混乱的种子。通过上面变量提升带来的问题,可以了解到在需要有块级作用域的时候,js的运行结果与c、java等拥有块级作用域语言的运行结果截然不同。随着JavaScript的流行,这门最初只为了让网页动起来而设计的语言逐渐暴露出更多的问题,最终为了能够解决这些问题,推出了ES6。

ES6在语言层面做了很大的调整,但为了保持向下兼容,就必须在支持旧规则的同时实现新的规则。针对变量提升来说,我们知道在编译阶段会将变量、函数提升并存放于执行上下文的变量环境对象中,在执行阶段通过在调用栈管理的一个个上下文环境的变量环境对象中查找目标变量/函数。可以说变量环境对象+调用栈支持了变量提升。在ES6中,为了保持兼容,选择在词法环境中自行维护一个类似调用栈的小型栈来管理块级作用域。在编译阶段,针对let、const关键字声明的变量,都将被放置到词法环境中,而var声明的变量或函数将被放置到变量环境对象中。

(四)从执行上下文角度分析块级作用域

通过代码示例分析,ES6如何结合let关键字支持块级作用域:
块级作用域

  1. 调用函数时开始编译函数体,只编译一次

  2. 进行变量提升,将a、c放入变量环境对象,将处于函数内部且非处于块级作用域内的变量压入词法环境维护的小型栈作为栈底。

  3. 对于{}块级作用域,对b、d进行提升并初始化为undefined,形成的对象暂不压入栈

  4. foo函数体生成可执行代码如下:

    a = 1;
    b = 2;
    {
        b = 3;
        c = 4;
        d = 5;
        console.log(a);
        console.log(b);
    }
    console.log(b);
    console.log(c);
    console.log(d);
    
  5. 执行可执行代码,更新变量环境对象:a = 1;更新词法环境小型栈栈底:b = 2;

  6. 执行到{}代码块,将块级作用域编译阶段形成的对象压入栈中,作为栈顶

  7. 更新词法环境小型栈栈顶:b = 3;

  8. 从词法环境小型栈栈顶向下查找变量c,找不到则进入变量环境对象查找c。更新变量环境对象:c = 4;

  9. 更新词法环境小型栈栈顶:d = 5;

  10. 先查找词法环境,再查找变量环境对象,查找a,打印值

  11. 打印b,查找方式同10

  12. {}代码块执行完毕,将块级作用域编译阶段形成的对象从栈顶弹出

  13. 继续执行函数体,打印b,先进入词法环境查找,再进入对象环境查找。

  14. 打印c

  15. 打印d时因查找不到变量而抛出异常

  16. 最终输出结果:1 3 2 4 抛出异常:Uncaught ReferenceError: d is not defined

总结:ES6通过在词法环境中维护一个类似调用栈的小型栈来支持块级作用域。当函数执行时,会将函数最外层通过let、const声明的变量,并且不处于块级作用域内的变量,进行变量提升,初始化为undefined,然后压入栈底。当运行带{}代码块时,再将该代码块通过let、const声明的变量追加入栈;代码块执行结束时又将它弹出栈顶销毁。当需要查找变量时,先从词法环境开始查找,自栈顶向下查找,若未命中目标,将进入变量环境查找。

(五)暂时性死区

在编译阶段已经有词法环境,变量也已经默认初始化为undefined,但在let声明该变量前使用,将会出现暂时性死区。如下:

function foo () {
    //debugger
    console.log("a", a);
    let a = 1;
}
foo()

暂时性死区示例
通过断点可以观察到在编译阶段,变量a被提升到函数顶并初始化为undefined;但上述代码最终执行结果是抛出异常:Uncaught ReferenceError: Cannot access ‘a’ before initialization,而不是打印出undefined。这是js引擎做的一个控制,通过let声明的变量,即使在编译阶段被提升并且初始化为undefined了,但就是不允许在代码执行到let声明前被访问。

总结:暂时性死区是语法规定,虽然通过let声明的变量在代码执行阶段已经存在词法环境中,但在执行到对应的let声明前访问该变量,js引擎就会抛出一个错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/517546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

01.LLaMA

文章目录 前言导读摘要 预备知识语言模型ChatGPT性能暴涨的原因&#xff08;涌现&#xff09;GPT-1Transformer 背景介绍模型精讲数据集及处理Common CrawlC4GithubWikipediaGutenberg and Books3ArXivStack Exchange小结 关键TrickPre-normalizationSwiGLURotary Embeddings 实…

判断浏览器是否支持webp图片

.WebP是谷歌主导的开放免费的网络图像格式&#xff0c;其核心编码来自VP8也就是同时支持WebP图片和WebM视频等。 这种图像格式追求的并不是无损画质&#xff0c;而是在有损画质的情况下尽可能的压缩图像体积但也尽量降低清晰度下降。 谷歌资助和发展该图像格式最主要的目的就是…

windows下升级nodejs

重新安装新版nodejs 重新安装nodejs然后设置环境变量 安装yarn npm install -g yarn --registryhttps://registry.npm.taobao.org yarn config set registry https://registry.npm.taobao.org -g yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sa…

IoT -- 解读物联网四层架构

本文以物联网四层架构为基础&#xff0c;从物联网产品设计的角度来解读每层架构的功能以及主要内容&#xff0c;旨在为物联网产品设计以及实现思路感兴趣的物联网产品或研发人员有些帮助。 通过互联网&#xff0c;人和人之间可以传递和交流信息。物联网&#xff0c;IoT&#x…

VMware Aria Automation 8.12 - 现代基础架构自动化平台

VMware Aria Automation 8.12 - 现代基础架构自动化平台 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-aria-automation/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 现代基础架构自动化平台 VMware Aria Au…

python 调用c语言方法详解

Python是一种强大的编程语言&#xff0c; Python在代码的编写中可以使用任何一种编程语言&#xff0c;可以利用其内置函数或模块来完成自己的任务。但是在 Python中调用其他语言的程序时&#xff0c;需要有特定的环境&#xff0c;例如&#xff1a;C、C等。 在 python中调用其他…

(转)雪花算法(SnowFlake)

简介 现在的服务基本是分布式、微服务形式的&#xff0c;而且大数据量也导致分库分表的产生&#xff0c;对于水平分表就需要保证表中 id 的全局唯一性。 对于 MySQL 而言&#xff0c;一个表中的主键 id 一般使用自增的方式&#xff0c;但是如果进行水平分表之后&#xff0c;多…

优化if-else代码的八种方案!

前言 代码中如果if-else比较多&#xff0c;阅读起来比较困难&#xff0c;维护起来也比较困难&#xff0c;很容易出bug&#xff0c;接下来&#xff0c;本文将介绍优化if-else代码的八种方案。 优化方案一&#xff1a;提前return&#xff0c;去除不必要的else 如果if-else代码块…

【花雕学AI】多方评测:有没有ChatGPT开发者模式?ChatGPT有可能被“越狱”吗?

学习ChatGPT过程中&#xff0c;出于好奇心&#xff0c;昨天晚上&#xff0c;第一次尝试使用那个据说能进入开发者模式的英文提示词&#xff0c;这应该是所见过最长的提示词了。经过不同平台的多次测试&#xff0c;总体感觉这是个比较细致的角色扮演&#xff08;扮演开发者模式&…

HTTPS传输过程中做了哪些事?

HTTPS介绍 https是是一种应用层协议&#xff0c;本质上来说是HTTP协议的一个升级版。HTTPS比HTTP更安全&#xff0c;HTTP是明文传输。HTTPS是加密传输。加密过程使用了三种加密手段&#xff1a;证书、对称加密、非对称加密。HTTPS相比于HTTP多了一层SSL/TSL&#xff0c;结构如下…

JUC并发编程17 | synchronized锁升级

尚硅谷&#xff08;121-139&#xff09; Synchronized 锁升级 入门简介 一些面试题&#xff1a; 谈谈你对 Synchronized 的理解 synchronized 的锁升级 在阿里的规范里&#xff1a; 高并发时&#xff0c;同步调用应该去考量锁的性能损耗。能用无锁数据结构&#xff0c;就…

【AI大模型】讯飞版大模型来了!首发通用人工智能评测体系,现场发布四大行业应用成果

文章目录 前言SparkDesk讯飞星火认知大模型简介语言理解知识问答逻辑推理数学题解答代码理解与编写亲自体验写在最后前言 5月6日,讯飞星火认知大模型成果发布会在安徽合肥举行。科大讯飞董事长刘庆峰、研究院院长刘聪发布讯飞星火认知大模型,现场实测大模型七大核心能力,并…

SoapUI简易使用

SoapUI简易使用 一、 什么是soapUI二、简单调用接口三、设置断言四、负载测试五、知识点 一、 什么是soapUI 由于 Web 服务是被程序调用的&#xff0c; 一般不会提供界面让最终用户或测试人员直接使用&#xff0c;soapUI是针对这种情况开发的一个工具&#xff0c;用户可以在 s…

python操作list

lst[1,2,3] print(lst) print(lst[0]) print(lst[-1]) print(lst[0:3:2]) # 判断 print(4 in lst) print(4 not in lst) # 遍历 for i in lst:print(i) # 添加 lst.append(测试) print(lst) # 添加一个列表 lst.extend() # 在任意位置添加一个元素 list.insert(2,12)# 删除操作…

进程信号(Linux)

进程信号 信号入门身边的信号进程信号 产生信号终端按键产生信号调用系统函数向目标进程发信号killraiseabort 硬件异常产生信号由软件条件产生信号 阻塞信号信号其他相关常见概念在内核中的表示sigset_t信号集操作函数sigprocmasksigpending 捕捉信号内核如何实现信号的捕捉si…

亚马逊云科技工业数据湖x创新应用,助您释放全新生产力

数字化浪潮蓬勃发展&#xff0c;制造行业数字化转型热度迭起&#xff0c;根据麦肯锡面向全球400多家制造型企业的调研表明&#xff0c;几乎所有细分行业都在大力推进数字化转型&#xff0c;高达94%的受访者都称&#xff0c;数字化转型是他们危机期间维持正常运营的关键。 数字化…

shell脚本之数组,冒泡排序算法

目录 一、数组 1.定义数组 2. 数组中数据类型 2.1数值类型 2.2字符类型 二、数组的用法 1. 输出数组中的值 2. 统计数组参数个数 ​编辑 3.查看数组下标列表 4.分割字符串 5.替换数组中的字符 6.删除数组 三、数组追加元素 1.方法1示例 2.方法2示例 3.方法3示例 …

SpringCloud Alibaba详解

目录 微服务架构概念 服务治理 服务调用 服务网关 服务容错 链路追踪 SpringcloudAlibaba组件 Nacos 负载均衡 Ribbon Fegin Sentinel 高并发测试 容错方案 Sentinel入门 Feign整合Sentinel 微服务架构概念 服务治理 服务治理就是进行服务的自动化管理&#xf…

MYSQL的主键和外键,内连接和外连接,关联子查询

目录 友情提醒第一章&#xff1a;MYSQL数据库多表主键和外键1&#xff09;外键介绍&#xff08;FOREIGN KEY&#xff09;2&#xff09;外键约束作用2&#xff09;三种情况下添加外键约束①一对一关系②一对多关系多对多关系 4&#xff09;删除外键约束 第二章&#xff1a;MYSQL…