深入学习js之——执行上下文栈#3

深入学习js系列是自己阶段性成长的见证,希望通过文章的形式更加严谨、客观地梳理js的相关知识,也希望能够帮助更多的前端开发的朋友解决问题,期待我们的共同进步。

如果觉得本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。


开篇

作为一个JavaScript的程序开发者,如果被问到JavaScript代码的执行顺序,你脑海中是不是有一个直观的印象 -- JavaScript 是顺序执行的,可事实真的是这样的吗?

让我们首先看两个小例子:

var foo = function () {
  console.log('foo1');
}

foo();  // foo1

var foo = function () {
  console.log('foo2');
}

foo(); // foo2
复制代码
function foo() {
  console.log('foo1');
}

foo();  // foo2

function foo() {
  console.log('foo2');
}

foo(); // foo2
复制代码

刷过面试题目的都知道:

JavaScript引擎并非一行一行地分析和执行程序,而是一段一段地分析执行,当执行一段代码的时候,会进行一个准备工作。

比如我们熟悉的JavaScript中的变量提升比如函数提升都是在这个准备阶段完成的。

本文我们就来深入的研究一下,这一段一段中的是如何划分的呢?

到底JavaScript引擎遇到一段怎样的代码才会做"准备工作"呢?为了解答这个问题我们引入一个概念——执行上下文

执行上下文

如果你做过小学的阅读理解,肯定见到过这样的题目:联系上下文解释句子,这里的上下文指的可能是这个句子所在的段落,也可能是这个句子所在段落的临近段落。实际上,这里描述的是一个句子的语境和作用范围,联系类比到程序中我们可以作如下定义:

执行上下文是当前JavaScript代码被解析和执行时所在环境的抽象概念。

执行上下文的类型

执行上下文总共分为三种类型,有时候我们也叫做可执行代码(executable code)

  • 全局执行上下文: 只有一个,浏览器中的全局对象就是window对象,this指向这个全局对象。
  • 函数执行上下文: 存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
  • Eval 函数执行上下文: 指的是运行在eval函数中的代码,很少用而且不建议使用。

举个例子,当执行到一个函数的时候,就会进行准备工作,这里的"准备工作",让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。

执行栈

接下来问题来了,我们写的函数多了去了,如何管理创建的那么多执行上下文呢?所以 JavaScript 引擎创建了执行上下文栈(Execution context stack )ECStack 来管理执行上下文。

这里我们可以简单的认为 ECStack 是一个数组,类似这样:

ECStack = [];
复制代码

执行栈,也叫做调用栈,具有 LIFO(last in first out 后进先出) 结构,用于存储在代码执行期间创建的所有执行上下文。

  • 首次运行JavaScript代码的时候,会创建一个全局执行的上下文并Push到当前的执行栈中,每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push当前执行栈的栈顶。
  • 当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文的控制权将移动到当前执行栈的下一个执行上下文。

让我们看一段代码来理解这个过程:

var a = 'Hello World!';

function first() {  
  console.log('Inside first function');  
  second();  
  console.log('Again inside first function');  
}

function second() {  
  console.log('Inside second function');  
}

first();  
console.log('Inside Global Execution Context');

// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
复制代码
  • 当上述代码在浏览器加载时,JavaScript引擎创建了一个全局执行上下文并把它压入(push) 当前的执行栈。当遇到 first() 函数调用时,JavaScript引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部
  • 当从 first() 函数内部调用 second() 函数时,JavaScript引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部,当 second() 函数执行完毕,它的执行上下文会从当前栈弹出(pop),并且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。
  • first() 执行完毕,它的执行上下文从栈中弹出,控制流程到达了全局执行上下文。一旦所有的代码执行完毕,JavaScript引擎从当前栈中移出全局执行上下文。

下面这张图,能够更加清晰的解释上面这个执行过程

看两个思考题

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
复制代码
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
复制代码

两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?

答案就是执行上下文栈的变化不一样。

让我们模拟第一段代码:

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
复制代码

让我们模拟第二段代码:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
复制代码

checkscope()();这里对于这个函数的执行做一些解释

// checkscope()() 就相当于
var f = checkscope();
f();
复制代码

checkscope 函数执行,函数执行完毕后,该函数返回一个函数名,就相当于:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
复制代码

然后再执行的这个返回的函数,就相当于:

ECStack.push(<f> functionContext);
ECStack.pop();
复制代码

为了更详细讲解两个函数执行上的区别,我们需要探究一下执行上下文到底包含了哪些内容,我们需要更加深入了解变量对象的相关内容。

参考链接:

《理解 JavaScript 中的执行上下文和执行栈》

《JavaScript深入之执行上下文栈》

深入学习JavaScript系列目录

欢迎添加我的个人微信讨论技术和个体成长。

欢迎关注我的个人微信公众号——指尖的宇宙,更多优质思考干货

转载于:https://juejin.im/post/5c68cafff265da2dcf627395

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值