摘要:在中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈。因为执行中最先进入全局环境,所以处于栈底的永远是全局环境的执行上下文。
一、什么是执行上下文?
执行上下文(Execution Context): 函数执行前进行的准备工作(也称执行上下文环境)
JavaScript在执行一个“代码段”之前,即解析(预处理)阶段,会先进行一些“准备工作”,例如扫描JS中var定义的变量、函数名等,进而生成执行上下文。
name | - |
---|---|
变量对象(VO, variable object) | 当前函数定义的变量、函数、参数 |
作用域链(Scope chain) | 源代码定义时形成的作用域链 |
this |
JS中的“代码段”分为三种:全局代码段、函数体代码段、eval代码段。(注:ES6之前,JS不存在“代码块”作用域的概念,即除了函数之外所有“{}”里的代码,都属于全局作用域)
全局代码段“准备工作”包括:
1.变量、函数表达式 —— 变量声明,默认赋值为undefined; 2.this —— 赋值; 3.函数声明 —— 赋值。
函数体代码段“准备工作”包括:
1.变量、函数表达式 —— 变量声明,默认赋值为undefined; 2.this —— 赋值; 3.函数声明 —— 赋值; 4.参数 —— 赋值; 5.argument —— 赋值; 6.自由变量的取值作用域 —— 赋值。
evel()不推荐使用,所以不再分析evel代码段。
至此,“执行上下文”的定义可以通俗化为 —— 在执行代码段之前(预处理阶段),把将要用到的所有变量都事先拿出来,有的直接赋值,有的先用undefined占个空,这些变量共同组成的词法环境,即为执行上下文环境。
二、执行上下文栈javaScript是单线程语言,简单理解下单线程,就是同个时间段只能做一件任务,完成之后才可以继续下一个任务。
函数编程中,代码中会声明多个函数,对应的执行上下文也会存在多个。在JavaScript中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。
要简单理解栈的存取方式,我们可以通过类比乒乓球盒子来分析。如下图左侧。
栈遵循"先进后出,后进先出"的规则,或称LIFO ("Last In First Out") 规则。
如图所示,我们只能从栈顶取出或放入乒乓球,最先放进盒子的总是最后才能取出。
栈中"放入/取出",也可称为"入栈/出栈"。
总结栈数据结构的特点:
后进先出,先进后出
出口在顶部,且仅有一个
2.执行上下文栈ECS(Execution Context Stack)(函数调用栈)程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);
程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。
因为JS执行中最先进入全局环境,所以处于"栈底的永远是全局环境的执行上下文"。而处于"栈顶的是当前正在执行函数的执行上下文",当函数调用完成后,它就会从栈顶被推出(理想的情况下,闭包会阻止该操作,闭包后续文章深入详解)。
"全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底"
function foo () { function bar () { return "I am bar"; } return bar(); } foo();3.执行上下文的生命周期
执行上下文的生命周期有两个阶段:
创建阶段(进入执行上下文)
执行阶段(代码执行)
创建阶段:函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段
执行阶段:执行函数中代码时,此时执行上下文进入执行阶段
创建变量对象
函数环境会初始化创建Arguments对象(并赋值)
函数声明(并赋值)
变量声明,函数表达式声明(未赋值)
确定this指向(this由调用者确定)
确定作用域(词法环境决定,哪里声明定义,就在哪里确定)
执行阶段的操作
变量对象赋值
变量赋值
函数表达式赋值
2.调用函数
3.顺序执行其它代码
当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。
"创建阶段对函数声明做赋值,变量及函数表达式仅做声明,真正的赋值操作要等到执行上下文代码执行阶段"。
代码例子1:变量提升
function foo() { console.log(a); // 输出undefined var a = "I am here"; // 赋值 } foo();
// 实际执行过程
function foo() { var a; // 变量声明,var初始化undefined console.log(a); a = "I am here"; // 变量重新赋值 }
代码例子2:函数声明优先级
function foo() { console.log(bar); var bar = 20; function bar() { return 10; } var bar = function() { return 30; } } foo(); // 输出bar()整个函数声明函数声明,变量声明,函数表达式的优先级
函数声明,如果有同名属性,会替换掉
变量,函数表达式
函数声明优先 > 变量,函数表达式
4.执行上下文的数量限制(堆栈溢出)执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。
// 递归调用自身 function foo() { foo(); } foo(); // 报错: Uncaught RangeError: Maximum call stack size exceeded三、执行上下文流程图
如果你觉得这篇文章对你有所帮助,那就顺便点个赞吧,点点关注不迷路~
黑芝麻哇,白芝麻发,黑芝麻白芝麻哇发哈!
前端哇发哈
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/101802.html
摘要:令人困惑的是,文档中称,指定的回调函数,总是排在前面。另外,由于指定的回调函数是在本次事件循环触发,而指定的是在下次事件循环触发,所以很显然,前者总是比后者发生得早,而且执行效率也高因为不用检查任务队列。 一、定时器 除了放置异步任务的事件,任务队列还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做定时器(timer)功能,也就是定时执行的代码。 定时器功能主要由setTim...
摘要:只要指定过回调函数,这些事件发生时就会进入任务队列,等待主线程读取。三主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为事件循环。 一、任务队列 同步任务与异步任务的由来 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。 如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候C...
摘要:在这次执行期间,函数中的将指向。在刚刚的例子中,因为在调用构造函数的过程中,手动的设置了返回对象,与绑定的默认对象被丢弃了。在上面的例子中,一个赋值给了的函数称为匿名函数,返回了另一个箭头函数称为匿名函数。 一、引言 在执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此,一个函数中的...
摘要:本计划一共期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,点击查看前端进阶的破冰之旅本期推荐文章深入之执行上下文栈和深入之变量对象,由于微信不能访问外链,点击阅读原文就可以啦。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,今天是第二天。 本计划一共28期,每期...
摘要:首次运行代码时,会创建一个全局执行上下文并到当前的执行栈中。执行上下文的创建执行上下文分两个阶段创建创建阶段执行阶段创建阶段确定的值,也被称为。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,,今天是第一天 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进...
阅读 2554·2021-11-22 12:05
阅读 3440·2021-10-14 09:42
阅读 1674·2021-07-28 00:15
阅读 1981·2019-08-30 11:08
阅读 1475·2019-08-29 17:31
阅读 918·2019-08-29 16:42
阅读 2327·2019-08-26 11:55
阅读 2107·2019-08-26 11:49