Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入之执行上下文栈 #4

Open
mqyqingfeng opened this issue Apr 23, 2017 · 93 comments
Open

JavaScript深入之执行上下文栈 #4

mqyqingfeng opened this issue Apr 23, 2017 · 93 comments
Labels

Comments

@mqyqingfeng
Copy link
Owner

@mqyqingfeng mqyqingfeng commented Apr 23, 2017

顺序执行?

如果要问到 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

打印的结果却是两个 foo2

刷过面试题的都知道这是因为 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,比如第一个例子中的变量提升,和第二个例子中的函数提升。

但是本文真正想让大家思考的是:这个“一段一段”中的“段”究竟是怎么划分的呢?

到底JavaScript引擎遇到一段怎样的代码时才会做“准备工作”呢?

可执行代码

这就要说到 JavaScript 的可执行代码(executable code)的类型有哪些了?

其实很简单,就三种,全局代码、函数代码、eval代码。

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

执行上下文栈

接下来问题来了,我们写的函数多了去了,如何管理创建的那么多执行上下文呢?

所以 JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文

为了模拟执行上下文栈的行为,让我们定义执行上下文栈是一个数组:

ECStack = [];

试想当 JavaScript 开始要解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 globalContext:

ECStack = [
    globalContext
];

现在 JavaScript 遇到下面的这段代码了:

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();

当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。知道了这样的工作原理,让我们来看看如何处理上面这段代码:

// 伪代码

// fun1()
ECStack.push(<fun1> functionContext);

// fun1中竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2> functionContext);

// 擦,fun2还调用了fun3!
ECStack.push(<fun3> functionContext);

// fun3执行完毕
ECStack.pop();

// fun2执行完毕
ECStack.pop();

// fun1执行完毕
ECStack.pop();

// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext

解答思考题

好啦,现在我们已经了解了执行上下文栈是如何处理执行上下文的,所以让我们看看上篇文章《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();

是不是有些不同呢?

当然了,这样概括的回答执行上下文栈的变化不同,是不是依然有一种意犹未尽的感觉呢,为了更详细讲解两个函数执行上的区别,我们需要探究一下执行上下文到底包含了哪些内容,所以欢迎阅读下一篇《JavaScript深入之变量对象》。

下一篇文章

《JavaScript深入之变量对象》

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

@jDragonV

This comment has been minimized.

Copy link

@jDragonV jDragonV commented May 10, 2017

文中,"当遇到一个函数代码的时候,就会创建一个执行上下文"
是不是应该是:“遇到函数执行的时候,就会创建一个执行上下文”

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 10, 2017

嗯,是的,感谢指正。o( ̄▽ ̄)d

@kevinxft

This comment has been minimized.

Copy link

@kevinxft kevinxft commented May 13, 2017

感谢,讲的通俗易懂,就是少了个赞赏的地方,手动滑稽。

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 13, 2017

@kevinxft 哈哈,不奢求那么多,star一下就是对我的鼓励了~ (๑•̀ㅂ•́)و✧

@zaofeng

This comment has been minimized.

Copy link

@zaofeng zaofeng commented May 28, 2017

image
大神,这里有打印出来一个undefined,能解释一下吗 谢谢。已star

@qianlongo

This comment has been minimized.

Copy link

@qianlongo qianlongo commented May 28, 2017

@zaofeng 函数执行结束之后,如果没有显示地返回值,默认是undefined,chrome中会把函数执行的结果打印出来(不过应该只是打印最外层的那个函数)

function fn3 () {
  return true
}

function fn2 () {
  fn3()
}

function fn1 () {
  fn2()
}

fn1() // undefined

function fn3 () {
  return true
}

function fn2 () {
  return fn3()
}

function fn1 () {
  return fn2()
}

fn1() // true




@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 29, 2017

@qianlongo 十分感谢回答~ 正在写的 JavaScript 专题系列也有很多会涉及 underscore 的实现方法,多多交流哈~~~

@xumengzi

This comment has been minimized.

Copy link

@xumengzi xumengzi commented May 30, 2017

可执行代码那块,执行上下文那里
就叫做"执行上下文(execution contexts)"。
上下文contexts多了个字母s吧?

另外还有个问题请教

function test(){
  console.log('test1');
};
test();

function test(){
  console.log('test2');
};
test();

这个的执行上下文栈是怎样模拟的呢?它有函数提升呢?

@zaofeng

This comment has been minimized.

Copy link

@zaofeng zaofeng commented May 30, 2017

@qianlongo 谢谢解答

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 31, 2017

@JarvenIV 规范中的执行上下文的英文也是有 s 的,可以查看http://es5.github.io/#x10,说起来,应该首字母大写来着……

你举得例子肯定是有函数提升的,因为函数提升的原因,同名的会被后者覆盖,实际上只会执行第二次声明的函数,执行上下文栈也只会创建第二次声明的函数的执行上下文,关于覆盖的规则,下一篇文章讲变量对象也会涉及到~

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 31, 2017

@JarvenIV 感谢指出,我多篇文章的可执行上下文的英文都少了 "s",o( ̄▽ ̄)d

@qianlongo

This comment has been minimized.

Copy link

@qianlongo qianlongo commented May 31, 2017

@mqyqingfeng 多多交流,正在写underscore相关的文章。

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 1, 2017

@t2krew var foo = function (){}也是有变量提升的,只是这个变量的值是一个函数而已。
举个例子:

console.log(foo)
var foo = function(){}

就是因为有变量提升,才会打印 undefined,否则就是报错啦

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 1, 2017

@t2krew 我只是说了有准备工作,也没有说跟这个就有关系呐😂

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 1, 2017

@t2krew 哈哈~ 研究的时候,这种精神是十分有必要的~ o( ̄▽ ̄)d

@xdwxls

This comment has been minimized.

Copy link

@xdwxls xdwxls commented Jun 7, 2017

@mqyqingfeng 博主 作用域和执行上下文是两个概念,方便记忆,应该如何如何理解区分呢? 作用域基于函数,执行上下文基于对象?

@suoz

This comment has been minimized.

Copy link

@suoz suoz commented Jun 8, 2017

上面说了当执行一段代码的时候,会进行一个“准备工作”,比如第一个例子中的变量提升,和第二个例子中的函数提升。

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

那么所说的是指,由于JS是一段一段执行,执行上下文就是我们所理解的“段”。

建议将第一句话更为“当执行一段代码时,会进行一个‘准备工作’,这个工作不仅包含了预编译阶段的‘变量提升、函数提升’等,还包含了执行阶段~”

@cobish

This comment has been minimized.

Copy link

@cobish cobish commented Jan 17, 2019

有个疑问,思考题里的这个:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

return f(); 算不算是尾调用呢?如果是的话,checkscope 应该会被先弹出执行上下文栈吧?

@zhoubhin

This comment has been minimized.

Copy link

@zhoubhin zhoubhin commented Jan 30, 2019

@guanyingjie
命令式和声明式的代码在执行时是有区别的

@hanqizheng

This comment has been minimized.

Copy link

@hanqizheng hanqizheng commented Mar 19, 2019

您好,小白想请教一下

console.log(a())

function a() {
  ...
}

这样的情况下执行上下文栈的顺序是怎么样的?

还有另一个问题
如果

function a() {
  console.log('111')
}
a()

function a() {
  console.log('222')
}
a()

等同于

function a() {
  console.log('111')
}

function a() {
  console.log('222')
}

a() // 222
a() // 222

的话,那么在进入执行上下文(假设就在全局下)这个阶段VO中a 赋值为 function() {}这一步就完成了函数的覆盖吗?

@zhoubhin

This comment has been minimized.

Copy link

@zhoubhin zhoubhin commented Mar 20, 2019

@hanqizheng 函数提升之后,找到a()执行function a() { console.log('222') },那么此时输出222,再次执行a()并且输出222,结束。
可以结合楼上那位朋友@dsying 的动态演示图来分析。

@liuliuLiu161

This comment has been minimized.

Copy link

@liuliuLiu161 liuliuLiu161 commented Jun 14, 2019

请教下 作用域链 和 执行上下文的顺序。

@eightHundreds

This comment has been minimized.

@qq781245153

This comment has been minimized.

Copy link

@qq781245153 qq781245153 commented Jul 20, 2019

var a = [1];
function f(a){
a[100] = 3
a = [1,2,3];
}
f(a)
console.log(a);

为啥输出的不是[1,2,3]呢

@tfeng-use

This comment has been minimized.

Copy link

@tfeng-use tfeng-use commented Jul 30, 2019

执行上下文仅仅是准备工作吗(变量提升和函数提升这一块)?感觉不太通顺,执行上下文描述的是一个名词,而变量提升、函数提升这种准备工作更像是一个动作。执行上下文到底是一个区域范围,还是就是博主说的准备工作,求解

@iT-lijun

This comment has been minimized.

Copy link

@iT-lijun iT-lijun commented Aug 16, 2019

@qq781245153

var a = [1];
function f(a){ 
  a[100] = 3; // 因为f(a)是引用方式传参,即f函数内部对参数a的修改就是对原数组a的修改。
  a = [1,2,3]; // 这里仅仅是把引用参数变量a指向新数组[1,2,3],并不会覆盖原数组a。
}
f(a);
console.log(a); // 结果a肯定不是[1,2,3] 而是 [1, empty × 99, 3]
@Clearives

This comment has been minimized.

Copy link

@Clearives Clearives commented Aug 30, 2019

文中2道题的理解
Clearives/clearives.github.io#17

@hopeful03

This comment has been minimized.

Copy link

@hopeful03 hopeful03 commented Sep 14, 2019

写的可真好啊,我刚想了解执行上下文有哪些内容下篇文章就是了。自己最近一段时间来也一直在学javascript,有些概念觉得已经明白了,但是要自己写总结性笔记时发现笔记结构实在太差了。

@liy010

This comment has been minimized.

Copy link

@liy010 liy010 commented Oct 15, 2019

有个疑问,思考题里的这个:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

return f(); 算不算是尾调用呢?如果是的话,checkscope 应该会被先弹出执行上下文栈吧?

我也在想这个问题.

@lizhongzhen11

This comment has been minimized.

Copy link

@lizhongzhen11 lizhongzhen11 commented Oct 27, 2019

个人感觉第二个函数执行上下文入栈出栈应该是这样的:

  ECStack.push(<checkscope> functionContext);
  ECStack.pop();
  ECStack.push(<checkscope> functionContext);
  ECStack.push(<f> functionContext);
  ECStack.pop();
  ECStack.pop();
@7neves

This comment has been minimized.

Copy link

@7neves 7neves commented Nov 6, 2019

@mqyqingfeng 所谓的“准备工作”是不是就是代码的编译阶段呀,编译阶段过后的代码执行阶段才是创建“执行上下文环境”吧?“准备工作”应该不包含“执行环境”的创建吧?这点有点晦涩,不知道这么理解是否正确

@guanxc9876

This comment has been minimized.

Copy link

@guanxc9876 guanxc9876 commented Nov 18, 2019

ECStack.push( functionContext);
ECStack.pop();
ECStack.push( functionContext);
ECStack.pop();
这个里面checkscope函数结束后f函数怎么还能找到。。checkscope函数出栈后不是清空了吗?

@BlaStone

This comment has been minimized.

Copy link

@BlaStone BlaStone commented Dec 2, 2019

大佬想到个问题,如果执行代码中遇到不可执行的代码,那执行上下文栈会发生什么变化,当前执行的这个函数会直接从栈中弹出么@mqyqingfeng

@qq781245153

This comment has been minimized.

Copy link

@qq781245153 qq781245153 commented Dec 2, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.