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

说一说你对JS上下文栈和作用域链的理解? #14

Open
YvetteLau opened this issue May 28, 2019 · 42 comments
Open

说一说你对JS上下文栈和作用域链的理解? #14

YvetteLau opened this issue May 28, 2019 · 42 comments

Comments

@YvetteLau
Copy link
Owner

No description provided.

@tpxiang
Copy link

tpxiang commented May 28, 2019

上下文栈:
1、全局中上下文是唯一的,并且在浏览器关闭时出栈。
2、在执行全局代码的时候,会先创建一个全局的上下文,并且压入栈的顶部。
3、执行一个函数就会创建一个上下文,并且压入栈的顶部,当执行完这个函数之后,执行出栈操作,并且等待浏览器回收。
作用域:
首先从当前位置开始查询,如果当前位置没有,则继续向上级作用域查询,直到到达全局作用域为止。

@shenanheng
Copy link

var a = 1;
console.log(c);
function b() {
var c = 2;
console.log(a,c)
}
b();
上下文栈:(浏览器中)
1:先创建全局上下文并把this指向window;
2:当遇到函数的时候也会创建上下文

首先浏览器进行编译过程,创建了a变量值为未定义,创建了函数b,当执行到第一句时,给a赋值为1;当执行到b(),在b的函数里面创建了c,并定义了c的值为2,当执行到下一句时,在函数b中,并没有定义a,他会往上一级一级一级的向上寻找知道找到a为止,如果没有则会报错,这样一级一级的往上查询就形成了 作用域链;

@icssoa
Copy link

icssoa commented May 28, 2019

/*
山下文栈:
1.在全局代码执行前,JS就会创建一个栈来储存管理所有的执行上下文对象
2.在全局执行上下文后,压栈
3.在函数创建后,压栈
4.在函数执行完后,出栈
5.在所有的代码执行完后,栈中只剩window(全局上下文)
6.全局上下文在浏览器窗口关闭后出栈
*/

/*
作用域链:
当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链。
*/

let n = 1;
function fn() {
let n = 2;

function fn1() {
	let n = 3;
	console.log(n); // 3
}

function fn2() {
	console.log(n); // 2
}

fn1();
fn2();

}

fn();
console.log(n);

@cute1baby
Copy link

上下文环境是在我们调用函数的时候产生的,而不是定义函数的时候产生的。话不多说,举个栗子:

var a = 10;,
    fn,
    bar = function(){
        var b = 8;
        fn(b);
    }
    fn = function(b){
        var d = 10;
        console.log(b+ d);
    }
bar();

根据上面这段代码来分析上下文栈的问题。
刚进入这段代码的时候会创建一个全局的上下文环境,一直往下走看到bar()之前就完成了全局变量的赋值,这个时候创建了bar函数的上下文环境进入到bar函数内部,又接着往下走看到了fn(),在此之前bar函数内部完成其变量的赋值。此时创建fn函数的上下文环境且进入到fn的函数内部...在执行完fn函数并且离开该函数fn上下文环境也就销毁并到bar的上下文中,接着在执行完bar函数并且离开该函数bar上下文环境也就销毁并到全局的上下文中。
而执行上下文栈,其实就是一个进栈和出栈的过程,也就是当前处于活动中的那个上下文环境。

关于作用域链,首先要全局变量和局部变量的概念。全局变量是对于全局作用域来说的,局部变量是对于函数作用域来说的。在函数中引用一个变量的时候会先在当前作用域下寻找,如果没有找到就会去上层作用域中寻找,这样逐层找变量的过程,就会形成一条当前作用域下对该变量的一个寻值的链条,而产生的作用域链。
而对于全局变量和局部变量的划分,又需要明白一个变量的出生到死亡的过程。首先全局变量在正常情况下是一直都存在于内存中,除非手动将其清理。而局部变量通常情况下在离开函数后就会被销毁,他出生于函数作用域中,在离开该作用域后就被判为死亡。

但是在局部变量中有一种巧妙的设计可以让当前局部变量活下来,我们给了他一个神奇的名字 —— 闭包。闭包在百度中的解释是:能够读取其他函数内部变量的函数。看起来不太好理解。话不多说,举个栗子:

var bar = (function(){
    var a = 1;
    return function(){
        a++;
        console.log(a);
    }
})()
bar();

一个很简答的函数自执行,这里面就用到了闭包。闭包一般都有一个特征就是在函数的内部定义一个函数,并且将这个函数作为返回值进行返回。那么此时我们得到的变量bar,其实就是该函数的返回值。这样的设计可以将局部变量a存储在内存中,享受跟全局变量一样的待遇。不同的是:他只能被返回值函数所调用,除了该函数之外他不会对其他看上去很靓很帅的函数多瞧一眼。所以说这个存储在内存中的局部变量也是蛮忠贞不渝的。

@ght5935
Copy link

ght5935 commented May 28, 2019

  • 占个沙发
    等开奖

@lqzo
Copy link

lqzo commented May 28, 2019

执行上下文栈(Execution Context Stack)

在ECMASscript中的上下文有三种类型:global, function和eval。

每一种代码的执行都需要依赖自身的上下文。当然global的上下文可能涵盖了很多的function和eval的实例。函数的每一次调用,都会进入函数执行中的上下文,并且来计算函数中变量等的值。eval函数的每一次执行,也会进入eval执行中的上下文,判断应该从何处获取变量的值。

注意,一个function可能产生无限的上下文环境,因为一个函数的调用(甚至递归)都产生了一个新的上下文环境。

function foo(bar) {}
// 调用相同的function,每次都会产生3个不同的上下文
//(包含不同的状态,例如参数bar的值)
foo(10);
foo(20);
foo(30);

一个执行上下文可以激活另一个上下文,就好比一个函数调用了另一个函数(或者全局的上下文调用了一个全局函数),然后一层一层调用下去。逻辑上来说,这种实现方式是栈,我们可以称之为上下文堆栈。

激活其它上下文的某个上下文被称为 调用者(caller) 。被激活的上下文被称为被 调用者(callee) 。被调用者同时也可能是调用者(比如一个在全局上下文中被调用的函数调用某些自身的内部方法)。

当一个 caller 激活了一个 callee,那么这个 caller 就会暂停它自身的执行,然后将控制权交给这个 callee . 于是这个 callee 被放入堆栈,称为进行中的上下文 [running/active execution context] . 当这个 callee 的上下文结束之后,会把控制权再次交给它的 caller,然后caller会在刚才暂停的地方继续执行。在这个caller结束之后,会继续触发其他的上下文。一个 callee 可以用返回(return)或者抛出异常(exception)来结束自身的上下文。

如下图,所有的 ECMAScript 的程序执行都可以看做是一个执行上下文堆栈 [execution context (EC) stack]。堆栈的顶部就是处于激活状态的上下文。

执行上下文栈

当一段程序开始时,会先进入全局执行上下文环境[global execution context], 这个也是堆栈中最底部的元素。此全局程序会开始初始化,初始化生成必要的对象[objects]和函数[functions]. 在此全局上下文执行的过程中,它可能会激活一些方法(当然是已经初始化过的),然后进入他们的上下文环境,然后将新的元素压入堆栈。在这些初始化都结束之后,这个系统会等待一些事件(例如用户的鼠标点击等),会触发一些方法,然后进入一个新的上下文环境。

见下图,有一个函数上下文“EC1″和一个全局上下文“Global EC”,下图展现了从“Global EC”进入和退出“EC1″时栈的变化:

执行上下文栈的变化

ECMAScript运行时系统就是这样管理代码的执行。

关于ECMAScript执行上下文栈的内容请查阅本系列教程的第11章执行上下文(Execution context)。

如上所述,栈中每一个执行上下文可以表示为一个对象。让我们看看上下文对象的结构以及执行其代码所需的 状态(state) 。

作用域链(Scope Chains)

A scope chain is a list of objects that are searched for identifiers appear in the code of the context.
作用域链是一个 对象列表(list of objects) ,用以检索上下文代码中出现的 标识符(identifiers) 。

作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。

标示符[Identifiers]可以理解为变量名称、函数声明和普通参数。例如,当一个函数在自身函数体内需要引用一个变量,但是这个变量并没有在函数内部声明(或者也不是某个参数名),那么这个变量就可以称为自由变量[free variable]。那么我们搜寻这些自由变量就需要用到作用域链。

在一般情况下,一个作用域链包括父级变量对象(variable object)(作用域链的顶部)、函数自身变量VO和活动对象(activation object)。不过,有些情况下也会包含其它的对象,例如在执行期间,动态加入作用域链中的—例如with或者catch语句。[译注:with-objects指的是with语句,产生的临时作用域对象;catch-clauses指的是catch从句,如catch(e),这会产生异常对象,导致作用域变更]。

当查找标识符的时候,会从作用域链的活动对象部分开始查找,然后(如果标识符没有在活动对象中找到)查找作用域链的顶部,循环往复,就像作用域链那样。

var x = 10;

(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;
    // "x"和"y"是自由变量
    // 会在作用域链的下一个对象中找到(函数”bar”的互动对象之后)
    console.log(x + y + z);
  })();
})();

我们假设作用域链的对象联动是通过一个叫做__parent__的属性,它是指向作用域链的下一个对象。这可以在Rhino Code中测试一下这种流程,这种技术也确实在ES5环境中实现了(有一个称为outer链接).当然也可以用一个简单的数据来模拟这个模型。使用__parent__的概念,我们可以把上面的代码演示成如下的情况。(因此,父级变量是被存在函数的[[Scope]]属性中的)。

作用域链

以上内容来自文章: https://www.cnblogs.com/TomXu/archive/2012/01/12/2308594.html


水平不够,总结不出来好的描述,等一个简单易懂的理解。

@MissWXiang
Copy link

什么是执行上下文?
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
1.JS上下文栈
(1)JavaScript 中有三种执行上下文类型。
a.全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
b.函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
c.Eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

2.作用域链
2.1.什么是作用域
2.1.1.作用域是一个函数在执行时期的执行环境。
可以理解为:每一个函数在执行的时候都有着其特有的执行环境,ECMAScript标准规定,在javascript中只有函数才拥有作用域。换句话,也就是说,JS中不存在块级作用域
2.1.2.作用域中声明提前
2.2.作用域的组成

  1. 函数形参的声明。
    2.函数变量的声明
    3.普通变量的声明。
    4.函数内部的this指针赋值
    ......函数内部代码开始执行!
    (微信名:RUN)

@ahaow
Copy link

ahaow commented May 28, 2019

执行上下文总共分为三个类型:

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

执行上下文栈

函数多了,就有很多个函数执行上下文,每次调用函数创建一个新的执行上下文,而JavaScript引擎创建了执行上下文来管理执行上下文栈,也可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

执行流程:

  1. JavaScript执行在单线程上面,所有代码都是排队执行的;
  2. 一开始浏览器执行全局的代码时,首页会创建全局的执行上下文,压入执行栈的顶部;
  3. 每当进入一个函数的执行就会创建一个函数的执行上下文,并且将它压入执行栈的顶部,当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收;
  4. 浏览器的JS引擎总是访问栈顶的执行上下文
  5. 全局上下文只有唯一的一个,它在浏览器关闭时出栈;

作用域链

查找一个变量,首先在它当前的所在的函数作用域查找,如果没有,就一层一层向上找,直到找到全局作用域还没有找到,就宣布放弃。这种一层层的关系,就是 作用域

@AILINGANGEL
Copy link

AILINGANGEL commented May 28, 2019

上下文栈

上下文栈是存放执行环境的。执行环境定义了变量和函数能够访问的其他数据(存储在这个执行环境的变量对象上),主要分为全局执行环境和函数执行环境。当执行到一个函数的时候就会将这个函数的执行环境压入这个执行栈,当函数执行完成,将就这个这个执行环境出栈,回到上层的执行环境。

  • 全局执行环境是栈最底部的一个执行环境,只会在应用程序退出(关闭网页或者浏览器)的时候才会退出
  • 局部执行环境里面的代码执行完成了就出栈了,并且被销毁了

作用域链

每个执行环境都会有一个变量对象,用来存储这个环境中声明的变量或者函数。在上下文栈中执行的各个环境之间的变量对象之间会创建一个关系(作用域链)。在当前执行环境中的代码可以访问当前这个变量对象或者变量对象向上关联的变量对象直到全局变量对象上存储的变量和函数。

简而言之:作用域是变量查找的规则,作用域链定义了变量可以查找的范围。变量查找只能从当前作用域开始向上查找而不能向下, 并且变量的查找具有遮蔽效应,在作用域链上进行向上查找的时候,最先找到的同名变量会遮蔽更上层作用域链中的同名变量.

附上一张我根据自己理解画的一张图

image

@jodiezhang
Copy link

1.执行上下文栈(Execution Context Stack(ECS))是执行JS代码时创建的执行栈结构。
2.全局执行上下文(Global Execution Context(GEC))全局执行上下文是文件第一次加载到浏览器,JS代码开始执行的默认上下文。在浏览器环境中,严格模式下this的值指向undefined,否则this的值为window对象。全局执行上下文只能有一个。
3.函数执行上下文(Functional Execution Context(FEC))函数执行时创建的执行上下文,每个函数都有自己的执行上下文。函数执行上下文可以获取到全局执行上下文中的内容。当在全局上下文中执行代码时JS引擎发现一个函数调用,则创建一个函数执行上下文。
3.Eval--先不做分析

全局执行上下文默认在执行上下文栈的最里面,当JS引擎发现一个函数调用,则创建这个函数的函数执行上下文,并把这个上下文push进栈,JS引擎执行栈顶上下文关联的函数,一旦函数执行完毕,则将其pop出栈,并往下执行。

我们再分析一下 函数执行上下文的两个阶段:创建阶段和执行阶段
创建阶段又叫编译阶段

  1. 创建Variable Object
    包含所有变量,函数参数和内部函数声明信息的特殊变量
  2. 创建作用域链
    一旦Variable Object创建完毕,JS引擎就开始初始化作用域链。作用域链是一个当前函数所在的可变对象的列表,其中包括全局作用域的可变对象和当前函数的可变对象。
    3.决定this的值
    初始化this值

执行阶段
在此阶段,JS引擎会重扫一边函数,用具体的变量值来更新可变对象,并执行代码内容

作用域链

a = 1;

var b = 2;

cFunc = function(e) {
  var c = 10;
  var d = 15;
  
  console.log(c);
  console.log(a); 
  
  function dFunc() {
    var f = 5;
    console.log(f)
    console.log(c);
    console.log(a); 
  }
  
  dFunc();
}

cFunc(10);

当cFunc被调用时,cFunc的作用域链是
Scope chain of cFunc=[cFunc variable object,Global Execution Context variable object]
当dFunc被调用时,dFunc在cFunc中,dFunc的作用域包含dFunc,cFunc和全局可变对象

Scope chain of dFunc = [dFunc variable object, 
                    cFunc variable object,
                    Global execution context variable object]

当访问dFunc中的变量f时,JS引擎首先查看dFunc如果存在就打印。访问c变量时,js引擎首先在dFunc的可变对象中获取,不能获取,就到cFunc的可变对象中获取,找到就打印出来

文章引用:
https://segmentfault.com/a/1190000016326483
https://www.cnblogs.com/wangfupeng1988/p/3994065.html
https://www.html.cn/archives/7255

@CCZX
Copy link

CCZX commented May 28, 2019

JavaScript执行上下文是JavaScript代码执行时的一个虚拟的环境,JavaScript首先会创建一个全局的执行上下文,如果后面遇到函数执行就创建该函数的执行上下文,并压入执行栈栈顶,当函数执行完成之后就从栈顶移除。JavaScript采用的是静态作用域,如果在访问某个变量的时候在自己的作用域中没有查询到,就会顺着作用域链向上查找知道全局作用域。

@TTTTTTTZL
Copy link

JavaScript是一门单线程的语言,所以它的执行顺行是由上往下执行的;

我们再看一段代码:

怎么执行效果不一样?

JavaScript的执行顺序由上往下执行的没错,但是javascript不是一行一行的分析和执行代码,而是一段一段的分析执行,并且在代码执行前会有一个 ‘准备工作’,变量提升和函数提升就是属于代码的准备工作!

而这里的’准备工作‘就是 ------> 执行上下文(execution context).

当函数写的多了,怎么来管理这么多的‘准备工作呢’?

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

我们用数组来模拟一下 执行上下文栈 的行为:
ECStack = [];

JavaScript运行时最先遇到的就是全局代码,所以初始化的时候会向 执行上下文栈 压入一个全局执行上下文,我们可以用globalContext来表示,当且仅当应用程序关闭的时候全局上下文才会消失。所以在程序关闭之前,ECStack最底下永远都有一个全局上下文 globalContext;

ECStack = [ globalContext ];

上一段实例代码:
;

//伪代码

//首先执行fun1();
ECStack.push(<fun1>functionContext);

//fun1中又调用了fun2;
ECStack.push(<fun2>functionContext);

//fun2中又又调用了fun3;
ECStack.push(<fun3>functionContext);

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

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

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

//javascript继续顺序执行下面的代码,但ECStack底部始终有一个 全局上下文(globalContext);

文章借鉴冴羽大神:mqyqingfeng/Blog#4

作用域链结合这两篇文章:
https://juejin.im/post/5abf5b5af265da23a1420833#heading-2
mqyqingfeng/Blog#6

@xdandsl
Copy link

xdandsl commented May 28, 2019

(一)执行上下文
可以理解成,执行JavaScript代码的一种环境。(个人理解为任何事情的发生都是在一定环境下发生的,执行js代码也是一样,必然是在某种环境下进行。)
上下文分为以下3类:
1)全局执行上下文:指的是浏览器中的全局对象window对象,只有这一个。
在严格模式下,this值为undifined,非严格模式下,this值即为window对象。
执行代码时,最先执行全局执行上下文终的代码。
2)函数执行上下文:指当调用函数时创建的函数执行上下文环境。
多个函数调用会产生多个函数执行上下文。
3)eval执行上下文:执行eval函数会创建执行上下文。只不过严格模式执行eval函数,不会作用于它的
外层作用域。

(二)执行上下文栈
可以理解成执行上下文栈是一个由js引擎创建存储函数调用的栈结构,遵循 先进后出的原则。(用来管理执行上下文)
1)执行全局代码之前,将全局上下文压入栈底。
2)开始执行全局代码,如果有调用函数情况,依次创建函数执行上下文,并执行压栈操作。执行函数。如果函数执行完毕,则将函数上下文pop出栈,并等待垃圾回收,然后往下执行代码。
3)等全部代码执行完毕,栈中只剩下全局执行上下文。他会等到浏览器关闭时出栈。

(三)作用域
es5中:1)全局作用域
//最外层函数(全局函数)和在最外层函数外面定义的变量(声明一个变量)拥有全局作用域
// 所有末定义直接赋值的变量自动声明为拥有全局作用域
//所有window对象的属性拥有全局作用域
2)函数作用域。
//函数内部声明的变量只有在函数内部可以访问,全局作用域和上一层的函数作用域无法访问。
es6中:块级作用域。
//通过let和const来实现,声明的变量,其作用域都是块级。
(四)作用域链
用于查找某个变量。
函数中查找某个变量,如果当前作用域没有,就到其上一次作用域找,找到即返回,没有继续往上找,直到找到全局作用域,找到就返回,没有就报错。
var a = 1
function fun1(a){
a = 3
console.log(a) //3
function fun2(a){
console.log(a) //3
console.log(b) //报错
}
fun2(a)
}

fun1(a)
console.log(a) //正常为1,因为前面报错没打印

@luohong123
Copy link

luohong123 commented May 28, 2019

一、JS上下文栈(Execution context stack,ECS)

理解执行上下文和栈可以让您了解为什么代码运行的结果和你最初预期的不同的原因。
JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。浏览器中的JS解释器是单线程的。也就是说在浏览器中同一时间只能做一个事情,其他的action和event都会被排队放入到执行栈中

可执行代码

可执行代码包括三种,全局代码、函数代码、eval代码。当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。

  • 全局代码(Global Code):代码首次执行时候的默认环境
  • 函数代码(Function Code):每当执行流程进入到一个函数体内部的时候
  • eval代码(Eval Code):当eval函数内部的文本执行的时候

执行上下文栈

关于执行栈,有5点需要记住:
单线程
同步执行
一个全局上下文
无数的函数上下文
每次函数调用都会床架一个新的执行上下文,即使是调用自身

二、作用域链

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

三、参考文章

【冴羽:JavaScript深入之执行上下文栈】(mqyqingfeng/Blog#4)
【冴羽: JavaScript深入之作用域链】(mqyqingfeng/Blog#6)

@Cain-kz
Copy link

Cain-kz commented May 28, 2019

上下文栈:当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
作用域链:作用域链与一个执行上下文相关,是内部上下文所有变量对象(包括父变量对象)的列表,用于变量查询。
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
console.log(x + y + z);
};
bar()
};
foo();
上面代码的输出结果为"60",函数bar可以直接访问"z",然后通过作用域链访问上层的"x"和"y"。

@gaoluona
Copy link

每一次代码执行和函数调用都会产生一个执行环境,称为执行上下文(context stack)。
一个执行上下文caller又可以激活(调用)另一个执行上下文callee,这时caller会暂停自身的执行把控制权交给callee进入callee的执行上下文,callee执行完毕后将控制权交回caller,callee可以用return或者抛出Exception来结束自己的执行。
多个执行上下文会形成执行上下文栈,最顶层是当前执行上下文,底层是全局执行上下文。

作用域(scope chain)是每一个执行上下文自身持有的活动对象的集合,如在本执行上下文中声明的变量和函数以及方法参数传入的对象。
每一个执行上下文可以访问的对象包括自身的作用域和父执行上下文的作用域和父父执行上下文作用域直到全局作用域,这就产生了作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。

文章引用: JS 执行上下文栈 / 作用域链

@Liruimin2
Copy link

Liruimin2 commented May 28, 2019

js上下文包括全局执行环境,函数执行环境。
var name1="haha";
2 function changName(){
3 var name2="xixi";
4 console.log(name1); // haha
5 console.log(name2);// xixi
6 }
7 changName();
8 console.log(name1);//haha
9 console.log(name2);//Uncaught ReferenceError: name2 is not defined
上述代码中,一共有三个执行环境:全局环境、changeName()的局部环境和 swapName() 的局部环境。所以,

 1.函数 swapName()的作用域链包含三个对象:自己的变量对象----->changeName()局部环境的变量对象 ----->全局环境的变量对象。

 2.函数changeName()的作用域包含两个对象:自己的变量对象----->全局环境的变量对象。

就上述程序中出现的变量和函数来讲(不考虑隐形变量):

 1.swapName() 局部环境的变量对象中存放变量 tempName;

 2.changeName() 局部环境的变量对象中存放变量 name2 和 函数swapName();

 3.全局环境的变量对象中存放变量 name1 、函数changeName();
作用域链一般会沿着自己当前的作用域查找变量,找不到会逐层向上查找,父级作用域链找不到会向全局作用域找,知道找到为止。作用域分全局作用域和局部作用域,全局作用域定义的变量会再局部作用域中访问到,而局部定义的变量由于作用域只能在局部作用域中访问

@riluocanyang
Copy link

执行上下文 & 栈

执行上下文主要分为全局上下文、函数上下文和eval上下文。
代码执行时,首先会将全局上下文压入栈中,也就是全局上下文栈,全局上下文栈是唯一的。
执行函数时,会将函数压入栈中,形成函数上下文栈。函数的每次调用,都会产生一个新的上下文,包括自身调用。

作用域链

每一个作用域与当前上下文有关,它包括了当前上下文的定义的变量,函数,以及传入的参数。
作用域分为全局作用域和函数作用域,全局作用域的属性和变量会挂载在window中。在全局作用域中,无法访问函数作用域内部的变量,但是在函数内部可以访问全局作用域变量。
由于在函数内部可以访问其父级作用域的变量,这样,就产生了作用域链,也就是当我们查找某个变量时,如果当前作用域没有,那么就会向上查找(父级作用域->父父级作用域),直到全局作用域。

@chongyangwang
Copy link

chongyangwang commented May 28, 2019

**** 1. js的上下文栈
听说过堆栈以及上下文执行环境 首次听说上下文栈
浏览器执行javascript代码是首先会创建一个执行环境 js的执行环境姑且看做两种吧
一种是全局执行环境 另一种是函数执行环境 全局执行环境有且只有一个都在window范围内 函数 执行环境可以有多个
执行环境都存放在栈中 什么是栈 ?
我的理解是 代码执行时开辟出来的一部分内存空间 将全局执行环境和函数执行环境依次存放于此空间 ,代码执行时 ,首先执行全局上下文 ,若全局上下文 ,有包括函数上下文 那么按上面列为大佬的说法就是 caller 会把访问权交给 callee 待callee执行完毕 访问权会重新归还caller 继续执行全局上下文 以此 待所有的函数执行环境执行完毕 那么全局执行环境最终执行完毕 此时 全局执行环境出栈 内存销毁。
**** 2. 作用域链
作用域链的概念一般存在于函数中 ,意味当在函数内部去访问某变量 且在当前函数内部 无法访问到 则会去当前函数的父级作用域访问 若父级作用域依旧找不到 那么一次向上访问 知道最后在window环境也无法找到 那么访问变量的值即为undefined
作用域链的查找只能向上 而不能向下。

@zhangxianhui
Copy link

运行机制

执行上下文

每段js 都有一个执行环环境 也就是常说的执行上下文

执行上下文 又分文 全局执行上下文 和 函数执行上下文

全局上下文 也就是最外层的执行环境 代码一开始就会先进入 执行的

函数上下文 当代码执行到一个函数体的时候 就进入了函数执行的上下文

执行上下文记录了代码运行时的环境,当前运行状态下 有且只有一个执行上下文起作用,问题执行上下文记录了什么了什么那? 也就是我们所说的词法环境 变量环境
来个例子看下
var x =1;
function foo(){
var y =2;
function a(){
var z =3;
}
a()
}
foo()

执行栈(调用栈)

代码运行时 首先会进入全局上下文 然后会执行到foot()时 就进入了 foo的上下文 ,然后代码继续执行 执行到a() 此时进入了 a的上下文 执行完a()后 又回到了 foo 上下文 执行文foo() 后 又回到了 全局上下文,
所以在执行过程中会行程一个执行栈 也叫 调用栈 他是先进后出

作用域链

作用域是一个函数在执行时期的执行环境,每个执行环境都会有一个变量对象,用来存储这个环境中声明的变量或者函数 在各个执行环境之间的这个对象会行程查找关系 也就是所说的作用域链

@darlingyz
Copy link

JS上下文栈:一个javascript程序中,必定会产生多个执行上下文,javascript引擎会以栈的方式来处理它们,也就是执行上下文栈
作用域链:JS引擎中,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链。
函数作用域是在函数声明的时候就已经确定了,而函数执行上下文是在函数调用时创建的
执行上下文的生命周期:
创建阶段
生成变量对象(Variable object, VO)
建立作用域链(Scope chain)
确定this指向
执行阶段
变量赋值
函数引用
执行其他代码
----写这么多吧,回头根据小姐姐的文章再总结下--

@muyiweibo
Copy link

上下是函数执行时产生的,es5中只有函数作用域,并没有块作用域,es6中let,const让变量有了块作用域

@yelin1994
Copy link

执行上下文栈

是由Javascript引擎所创建来管理执行上下文(每个执行文都包含三个重要属性,变量对象Ov,作用域链, this)

比如有以下脚本

    function a () {
        console.log('a')
    }
    function b () {
        console.log('b')
        a()
    }
    function c () {
        console.log('c')
        b()
    }
    c()

每执行一个函数,都会创建一个上下文,当执行以上代码时,执行栈事先会先把全局执行上下文压入栈中,再把函数c的执行上下文push进去,再push b的上下文,再push a的上下文。
且执行符合栈的特点,"先进后出",a 执行完毕后,出栈,一步步下去。

作用域链

当查找变量的时候,会从当前上下文的变量对象中查找,如果没找到会继续往父级的作用域查找,直到全局上下文。

函数内部有一个[[scope]] 属性,气主要功能是保存期父变量对象到其中

function a () {
    function b () {
        
    }
}
a[[scope]]存的是全局的变量对象而
b[[scope]] = [
a.vo,
global.vo
]
这边说明一下,VO不能被js直接访问,要进入函数上下文,创建VO/AO, AO 是一个激活的对象,
这时执行上下文的作用域链为[AO].concat([[scope]]),AO放于顶端

@web-data-MrLi
Copy link

https://juejin.im/post/58eaecdea0bb9f0069271861
这篇文讲述了底层的原理,
JS上下文栈:一个javascript程序中,必定会产生多个执行上下文,javascript引擎会以栈的方式来处理它们,也就是执行上下文栈
作用域链:JS引擎中,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链。
这是很通俗易懂的,但是如果理解其本质的内容的话,需要多加练习

@plane-hjh
Copy link

JS是一门单线程语言,从上往下执行的。但是代码并不是一句一句的往下面执行,而是一段一段代码分析执行,首先会经历编译阶段,接着才是执行阶段。

执行上下文

执行上下文总共有三种类型

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

执行上下文栈

因为JS引擎创建了很多的执行上下文,所以JS引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。

当 JavaScript 初始化的时候会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,执行栈才会被清空,所以程序结束之前, 执行栈最底部永远有个 globalContext。

作用域链

当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链。

@huangsiyuan2015
Copy link

执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。
JavaScript中的运行环境大概包括三种情况。

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval(不建议使用,可忽略)

在一个JavaScript程序中,必定会产生多个执行上下文,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

@ivan0525
Copy link

@MissNanLan
Copy link

MissNanLan commented May 28, 2019

执行上下文栈

简单的来说就是当我们在执行函数时,js引擎会为我们创建一个执行的上下文, 执行函数(入栈),函数结束(出栈),这个出栈入栈的过程就叫执行上下文 栈。一旦代码执行就会创建一个全局的执行上下文,也就最先会入一个全局执行上下文栈,每调用一个新的函数就会入一个新的执行上下文栈,一直到函数结束,它们一次动栈顶出栈,直到回到全局上下文栈

作用域链
说作用域链不得不说作用域,作用域就是一个独立的空间,ES6增加了块级作用域的概念,在这之前只有全局作用域和函数作用域

作用域链就是自由变量(当前没有定义的变量,要往上级找)一层一层往上找,直到找到全局变量为止

var a = 100 
function f1() { 
 var b = 200 
 function f2() { 
     var c = 300 
     console.log(a) // 自由变量,顺作用域链向父作用域找 
     console.log(c) // 本作用域的变量 
 } 
 f2() 
} 
f1() 

@YvetteLau
Copy link
Owner Author

YvetteLau commented May 28, 2019

执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。

执行上下文类型分为:

  • 全局执行上下文
  • 函数执行上下文
  • eval函数执行上下文(不被推荐)

作用域

作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。—— 摘录自《你不知道的JavaScript》(上卷)

作用域有两种工作模型:词法作用域和动态作用域,JS采用的是词法作用域工作模型,词法作用域意味着作用域是由书写代码时变量和函数声明的位置决定的。(witheval 能够修改词法作用域,但是不推荐使用,对此不做特别说明)

作用域分为:

  • 全局作用域
  • 函数作用域
  • 块级作用域

不要再说ES6之前没有块级作用域了!了解下with(不被推荐)和catch~

try {
    throw 2;
}catch(a) {
    console.log(a); //2
}
console.log(a); //ReferenceError: a is not defined

JS执行上下文栈(后面简称执行栈)

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

规则如下:

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

以一段代码具体说明:

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

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();

Global Execution Context (即全局执行上下文)首先入栈,过程如下:

作用域链

作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。

如:

var a = 10;
function fn1() {
    var b = 20;
    function fn2() {
        a = 20
    }
   return fn2;
}
fn1()();

@woyiweita
Copy link

执行上下文
顾名思义,执行上下文是JS在执行时创建的环境,当执行完成后就会被垃圾回收机制进行回收处理。
执行上下文分类
根据执行的不同,可以分为:

  • 全局执行上下文
  • 函数执行上下文
  • eval函数执行上下文(因为各个方面的因素,eval不推荐使用)

作用域
作用域和执行上下文是两个完全不同的概念,作用域是在JS代码编译的过程中就已经确定的,而执行上下文是在执行的过程中确定的。两者在应用的过程中会有比较大的区别。
比如在群里出现的这样一道题:

function foo(){
    console.log(a);
}
function bar(){
    var a = 20;
    foo();
}
var a = 10;
bar();
// => 10

在这里,如果foo()的作用域容易被误解,其实在foo函数创建的时候就已经确定了其作用域,因为函数内部没有声明a变量,所以向上查找是到了全局作用域,而非执行调用的 bar 函数内部作用域。这里需要重点理解。
作用域链
作用域链可以这样理解,当前作用域中没有查找到定义的该变量的时候就会向上查找是否定义该变量,直到查找到全局作用域为止。这里可以向上参考题主关于作用域链的回答。[又偷懒了....罪过]

@Diamondjcx
Copy link

1.什么是执行上下文?

执行上下文时评估和执行JavaScript代码的环境的抽象概念。每当JavaScript代码在运行的时候,它都是在执行上下文中运行。

2.执行上下文的类型

  • 全局执行上下文
    这是默认或者说基础的上下文,任何不在函数内部的代码都是在全局上下文中。它会执行两件事:创建一个全局的window对象,并且设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文
    每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤
  • Eval函数执行上下文
    执行在Eval函数内部的代码也会有它属于自己的执行上下文,但由于JavaScript开发者并不经常使用eval。

3.作用域链

函数与函数之间可以进行嵌套的,函数作用域的嵌套就组成了所谓的函数作用域链。当在自身作用域内找不到该变量的时候,会沿着作用域链逐步向上查找,若在全局作用域内仍找不到该变量,则会抛出异常。

@0xBrooke
Copy link

JS引擎是浏览器的重要组成部分,主要用于读取并执行js。就是这家伙执行js的,但它不止于执行js。
各大浏览器的JS引擎是什么,在这不详赘述,每个浏览器的JS引擎都不同,但他们执行js机制大致相同。
大家都知道JS是单线程一步步执行下来的,所以JS执行单线程是**,JS引擎会维护一个执行栈(先进后出)**。
那大家可能会问,什么是执行栈呢?执行栈就是将执行上下文按激活顺序依次压入到执行栈中,栈顶的执行上下文获得执行权,并按顺序执行当前上下文中的代码,执行完后弹栈销毁上下文,执行权交给下一个栈顶执行上下文。
举栗:
//运行代码

sayHello();
function sayHello(){
    var message = getMessage();
    console.log(message);
}
function getMessage(){
    return 'hello';
}

//执行栈
var exeStack = [];
//先压如全局执行环境
exeStack.push('globalContext');
//遇到执行sayHello函数,ok,压进去
exeStack.push('sayHello');
//执行sayHello函数发现,还有个getMessage函数,ok,压进栈
exeStack.push('getMessage');
//执行完了getMessage函数,弹栈
exeStack.pop();
//继续执行sayHello函数,又发现有console.log这个家伙,ok,你进栈
exeStack.push('console.log');
//执行了console后,输出hello,console 弹栈
exeStack.pop();
//这时sayHello执行完,弹栈
exeStack.pop();
//最后整个代码执行完,全局环境弹栈
exeStack.pop();

1144006-20171121120123649-80762518

这块还有个知识点:大家都知道闭包函数内存不会释放,那为什么内存不释放呢? 思考??????

说了这么半天,那什么是执行上下文?
又叫一个执行环境,有全局执行环境和函数执行环境两种。每个执行环境中包含这三部分:变量对象/活动对象,作用域链,this的值
我们上面说过,执行上下文在被执行是,会压入到执行栈中,但是之前还有一步工作要做,就是创建好执行上下文,因为创建好才能被压进去啊。
创建执行上下文的工作:

变量对象(VO): 浏览器在对代码进行解析时,会先进行变量声明和函数声明以及函数形参声明;
(全局作用域下)那么这些优先声明
的变量储存在哪里呢? 没错,就在变量对象中(抽象概念)
活动对象(AO):活动对象是在函数执行上下文里面的,其实也是变量对象,只是它需要在函数被调用时才被激活,而且初始化arguments,激活后就是看做变量对象执行上面一样的步骤。

function fn(name){
    var age = 3;
    console.log(name);
}
fn('ry');

//活动对象

AO = {
    arguments : {0:'ry'},  //arguments的值初始化为传入的参数
    name : ry,  //形参初始化为传进来的值
    age : undefined  //var 声明的age,赋值为undefined
}

作用域链: 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。
作用域链与变量对象有着密不可分的关系,因为作用域链就是变量对象的数组!其中第一个是当前函数的活动对象,第二个是当前活动函数的父亲上下文的活动对象,第三个是当前活动函数的爷爷上下文的活动对象,……,最后一个是全局上下文的活动对象。

this: 不在赘述 具体看 https://github.com/YvetteLau/Step-By-Step/issues/1

通过查阅资料总结学习到就这么多,有不对的地方请多多包涵。。。

@0uzu0
Copy link

0uzu0 commented May 30, 2019

javascript运行的代码环境有三种:

  • 全局代码:代码默认运行的环境,最先会进入到全局环境中

  • 函数代码:在函数的局部环境中运行的代码

  • Eval代码:在Eval()函数中运行的代码

作用域链:函数在执行时,每遇到一个变量,都会去执行上下文的作用域链的顶部,也就是执行函数的激活对象开始搜索,如果在第一个作用域链(即,Activation Object 激活对象)中找到了,那么就返回这个变量。如果没有找到,那么继续向下查找,直到找到为止。如果在整个执行期上下文中都没有找到这个变量,在这种情况下,该变量被认为是未定义的。

@zyq503454535
Copy link

执行栈上下文包含三种环境:
全局执行上下文:当js执行时压入到函数底部,当网页关闭时,执行完成
函数执行上下文:每当函数的调用时,就会创建一个新的函数执行上下文
eval:在Eval()函数中运行的代码
作用域链:指的是一个对象列表(a list of object), 主要用来检索上下文中出现的标识符

@jackluson
Copy link

上下文栈

js代码执行有三种环境,对应着三种上下文:

  • 全局代码 -- 代码默认运行的环境,最先会进入到全局环境中
  • 函数代码 -- 每当函数的调用时,就会创建一个新的函数执行上下文
  • eval代码 -- 代码默认运行的环境,最先会进入到全局环境中

执行上下文栈也就是入栈出栈操作(这个这里就不展开说了),函数定义时,上下文是没有确定的,只有调用执行时,才能确定执行上下文(this的指向)

作用域

来自题主回答
作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。—— 摘录自《你不知道的JavaScript》(上卷)

作用域有两种工作模型:词法作用域和动态作用域,JS采用的是词法作用域工作模型,词法作用域意味着作用域是由书写代码时变量和函数声明的位置决定的。(with 和 eval 能够修改词法作用域,但是不推荐使用,对此不做特别说明)

作用域链

作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 (类似原型链的查找方式)。

@954545647
Copy link

JS的上下文环境有:

  1. 全局环境
  2. 函数环境
  3. eval环境
    每进入一个运行环境都会创建一个相应的执行上下文,js引擎会形成一个函数调用栈来存放这些执行上下文,栈是先进后出,所以栈底永远是全局执行上下文,栈顶是当前执行上下文

作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域,如果全局作用域也没有就会报错

@KRISACHAN
Copy link

KRISACHAN commented Jun 2, 2019

说一说你对JS上下文栈和作用域链的理解?

词法环境

词法环境(Lexical Environment) 就是 ECMAScript 的代码环境。

它由 环境记录(Environment Record)外部引用 组成。

它会根据 ECMAScript 代码来动态创建。

环境记录绑定了其关联的词法环境。其一共分了5类,分别如下:

  • 声明环境记录(Declarative Environment Records)声明环境记录(Declarative Environment Records) 绑定变量声明,其中包含:varletconstclassmoduleimportfunction
  • 对象环境记录(Object Environment Records)对象环境记录(Object Environment Records) 记录每个对象增删改查。
  • 函数环境记录(Function Environment Records)函数环境记录(Function Environment Records) 每个 函数作用域 以及 上下文环境的具体情况。
  • 全局环境记录(Global Environment Records)全局环境记录(Global Environment Records) 用于表示所有 ECMAScript Script元素 共享的 最外层作用域 。全局环境记录提供内置全局变量,全局对象的属性以及脚本中发生的所有顶级声明的绑定。
  • 模块环境记录(Module Environment Records)模块环境记录(Module Environment Records) 用于表示 ECMAScript模块 的外部范围以及绑定情况。

词法环境分了三类:

  • 全局环境(global environment)全局环境(global environment) 是一个没有外部环境的 词法环境全局环境(global environment)的外部环境引用为 null。全局环境(global environment) 的EnvironmentRecord(环境记录) 可以绑定变量,并关联对应的 全局对象
  • 模块环境(module environment)模块环境(module environment) 也是一个 词法环境 ,它包含模块顶级声明的绑定。它还包含模块显式导入的绑定。模块环境的外部环境是 全局环境(global environment)
  • 函数环境(function environment)函数环境(function environment) 也是一个 词法环境 ,对应于 ECMAScript 函数对象的调用。函数环境(function environment) 可以建立新的此绑定。函数环境还支持 super 调用所需的状态。

执行上下文(Execution Contexts)

执行上下文(Execution Contexts)ECMAScript 代码 运行时(runtime) 的上下文环境。

在同一时间内,每次只能有一个 执行上下文(Execution Contexts) 运行。这称为 运行执行上下文(running execution context)

执行上下文堆栈(execution context stack) 用于跟踪 执行上下文(Execution Contexts)

正在运行的 执行上下文(Execution Contexts) 始终是此堆栈的顶级元素。

每当控制从与当前运行的执行上下文相关联的可执行代码转移到与该执行上下文无关的可执行代码时,就创建新的执行上下文。

新创建的执行上下文被压入堆栈并成为正在运行的 执行上下文

所有执行上下文的状态组件(State Components)

组件 作用
code evaluation state 用来判断当前执行上下文的状态
Function 判断当前执行上下文状态,如果当前上下文是函数,则上下文环境为此函数对象,否则则为null
Realm 相关代码从中访问 ECMAScript 代码的领域(Realm)记录。
ScriptOrModule 判断当前代码环境,如果没有所处的脚本或模块环境,就像在 InitializeHostDefinedRealm(原生方法) 中创建的原始执行上下文的情况一样,使上下文环境为 null

作用域链(Scope chain)

作用域 负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。-- 《你不知道的JavaScript(上卷)》

从上面两个话题,我们可以知道,除 全局作用域(global scope) 外,每个作用域始终连接到其背后的一个或多个作用域,从而形成 作用域链(scope chain)全局作用域(global scope) 没有任何父级,这也是有意义的,因为它位于层次结构的顶部。

我们看下面的代码:

const bestAvenger = "Iron man";
function a() {
  const bestActor = "Neymar";
  console.log(bestAvenger); // output:Iron man
    
  function c() {
    const bestProgrammingLanguage = "Html";
    console.log(bestActor); // output:Neymar
    b();
  }
  c();
}
function b() {
  console.log(bestProgrammingLanguage); //**not defined error**
}
a();

上面的代码会报错如下:bestProgrammingLanguage is not defined.

如上所述,作用域链(Scope chain) 始终是 词法 创建的。作用域 的父节点由 执行上下文(函数) 在代码中的词法或物理位置定义。

上面代码的 作用域链(Scope chain) 如下:

词法环境 作用域链
全局作用域下的G G = G
G下的A A = A + G
G下的B B = B + G
G下的A,A下的C C=C+A => C+A+G

其实总结起来就是:

  • 每当编译器遇到变量或对象时,它都会遍历当前执行上下文的整个 作用域链(Scope chain) ,如果没有在那里找到它,就遍历 原型链(prototype chain),如果也没有找到,它会抛出未定义的错误。
  • 编译器通过查看函数在代码中的位置来创建函数的作用域。
  • 编译器创建 作用域链(Scope chain)全局作用域(Global Scope) 位于此层次结构的顶部。
  • 当代码中使用变量时,编译器会向后查看作用域链,如果找不到,则抛出未定义的错误。

参考资料

  1. ECMA文档
  2. Javascript Scope Chain and Execution Context simplified

@DazhiFe
Copy link

DazhiFe commented Jun 3, 2019

上周这题还没有解答,这周补上哈。

@noggcool
Copy link

noggcool commented Jun 4, 2019

上下文栈是用来存放执行上下文的,也可以理解为是一个存储函数调用的栈结构。
执行上下文:是js代码被解析和执行时所产生的抽象概念,换句话说,只要代码执行(函数调用)都会产生执行上下文,执行上下文环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个函数都有自己的执行环境。
执行上下分类:
1.全局执行上下文
2.函数上下文
3.Eval函数执行上下文
执行流程:
1.函数对应上下文会在栈中排队。所以可以理解到JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。浏览器中的JS解释器是单线程的
。也就是说在浏览器中同一时间只能做一个事情,其他的action和event都会被排队放入到执行栈中。
2.浏览器执行解析代码时,先创建全局上下文。其他函数执行时会创建上下文依次压入栈顶,执行完后,出栈,等待垃圾回收。
3.后进先出
4.js引擎总是访问栈顶的执行上下文。
5.全局上下文只有唯一的一个,浏览器关闭时出栈。

作用域链:
和作用域链相关的几个东西:执行环境、变量对象
执行环境上文有说。
变量对象:每个执行环境都有一个变量对象,环境中定义的所有变量和函数都保存在这个对象中。
当代码在环境中执行时,会创建变量的一个作用域链。查找一个变量先在自己所在的作用域查找,找不到会继续像上层父作用域链查找,若找不到一直网上知道顶级父及window结束。若找到,则使用最近的作用域链中的变量。JavaScript中,if、for语句中的变量声明会将变量添加到当前的执行环境中。

@taoyaoyaoa
Copy link

执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。 Javascript 代码在运行的时候,就是在执行上下文中运行。
作用域链

在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

@gdh51
Copy link

gdh51 commented Sep 15, 2021

下为KRISACHAN回答的引用:

它由 环境记录(Environment Record)外部引用 组成。

环境记录是个抽象概念,词法环境属于环境记录的一种,相当于其子类。你这里说错了。

The LexicalEnvironment and VariableEnvironment components of an execution context are always Environment Records.

一个环境记录具有[[OuterEnv]]属性来对外部环境记录进行引用。所以这里应该是词法环境是一种环境记录,它由对外部环境记录引用与当前环境内的标识与变量组成的作用域组成。

参考:9.3 Execution Contexts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests