Step-By-Step
Step-By-Step copied to clipboard
说一说你对JS上下文栈和作用域链的理解?
上下文栈:
1、全局中上下文是唯一的,并且在浏览器关闭时出栈。
2、在执行全局代码的时候,会先创建一个全局的上下文,并且压入栈的顶部。
3、执行一个函数就会创建一个上下文,并且压入栈的顶部,当执行完这个函数之后,执行出栈操作,并且等待浏览器回收。
作用域:
首先从当前位置开始查询,如果当前位置没有,则继续向上级作用域查询,直到到达全局作用域为止。
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为止,如果没有则会报错,这样一级一级的往上查询就形成了 作用域链;
/* 山下文栈: 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);
上下文环境是在我们调用函数的时候产生的,而不是定义函数的时候产生的。话不多说,举个栗子:
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存储在内存中,享受跟全局变量一样的待遇。不同的是:他只能被返回值函数所调用,除了该函数之外他不会对其他看上去很靓很帅的函数多瞧一眼。所以说这个存储在内存中的局部变量也是蛮忠贞不渝的。
- 占个沙发 等开奖
执行上下文栈(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
水平不够,总结不出来好的描述,等一个简单易懂的理解。
什么是执行上下文? 简而言之,执行上下文是评估和执行 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.作用域的组成
- 函数形参的声明。
2.函数变量的声明
3.普通变量的声明。
4.函数内部的this指针赋值 ......函数内部代码开始执行!
(微信名:RUN)
执行上下文总共分为三个类型:
- 全局执行上下文:只有一个,浏览器中的全局对象就是window对象,this就是指向这个全局对象;
- 函数执行上下文:可以存在无数个,只有在函数被调用的时候才能被创建,每次调用的函数都会创建一个新的执行上下文;
- Eval函数执行上下文:指的是运行在Eval函数中的代码,很少用而且不建议
执行上下文栈
函数多了,就有很多个函数执行上下文,每次调用函数创建一个新的执行上下文,而JavaScript
引擎创建了执行上下文来管理执行上下文栈,也可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出
的原则
执行流程:
- JavaScript执行在单线程上面,所有代码都是排队执行的;
- 一开始浏览器执行全局的代码时,首页会创建全局的执行上下文,压入执行栈的顶部;
- 每当进入一个函数的执行就会创建一个函数的执行上下文,并且将它压入执行栈的顶部,当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收;
- 浏览器的JS引擎总是访问栈顶的执行上下文
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈;
作用域链
查找一个变量,首先在它当前的所在的函数作用域查找,如果没有,就一层一层向上找,直到找到全局作用域还没有找到,就宣布放弃。这种一层层的关系,就是 作用域
上下文栈
上下文栈是存放执行环境的。执行环境定义了变量和函数能够访问的其他数据(存储在这个执行环境的变量对象上),主要分为全局执行环境和函数执行环境。当执行到一个函数的时候就会将这个函数的执行环境压入这个执行栈,当函数执行完成,将就这个这个执行环境出栈,回到上层的执行环境。
- 全局执行环境是栈最底部的一个执行环境,只会在应用程序退出(关闭网页或者浏览器)的时候才会退出
- 局部执行环境里面的代码执行完成了就出栈了,并且被销毁了
作用域链
每个执行环境都会有一个变量对象,用来存储这个环境中声明的变量或者函数。在上下文栈中执行的各个环境之间的变量对象之间会创建一个关系(作用域链)。在当前执行环境中的代码可以访问当前这个变量对象或者变量对象向上关联的变量对象直到全局变量对象上存储的变量和函数。
简而言之:作用域是变量查找的规则,作用域链定义了变量可以查找的范围。变量查找只能从当前作用域开始向上查找而不能向下, 并且变量的查找具有遮蔽效应,在作用域链上进行向上查找的时候,最先找到的同名变量会遮蔽更上层作用域链中的同名变量.
附上一张我根据自己理解画的一张图
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出栈,并往下执行。
我们再分析一下 函数执行上下文的两个阶段:创建阶段和执行阶段 创建阶段又叫编译阶段
- 创建Variable Object 包含所有变量,函数参数和内部函数声明信息的特殊变量
- 创建作用域链 一旦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
JavaScript执行上下文是JavaScript代码执行时的一个虚拟的环境,JavaScript首先会创建一个全局的执行上下文,如果后面遇到函数执行就创建该函数的执行上下文,并压入执行栈栈顶,当函数执行完成之后就从栈顶移除。JavaScript采用的是静态作用域,如果在访问某个变量的时候在自己的作用域中没有查询到,就会顺着作用域链向上查找知道全局作用域。
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);
文章借鉴冴羽大神:https://github.com/mqyqingfeng/Blog/issues/4
作用域链结合这两篇文章: https://juejin.im/post/5abf5b5af265da23a1420833#heading-2 https://github.com/mqyqingfeng/Blog/issues/6
(一)执行上下文 可以理解成,执行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,因为前面报错没打印
一、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深入之执行上下文栈】(https://github.com/mqyqingfeng/Blog/issues/4) 【冴羽: JavaScript深入之作用域链】(https://github.com/mqyqingfeng/Blog/issues/6)
上下文栈:当 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"。
每一次代码执行和函数调用都会产生一个执行环境,称为执行上下文(context stack)。 一个执行上下文caller又可以激活(调用)另一个执行上下文callee,这时caller会暂停自身的执行把控制权交给callee进入callee的执行上下文,callee执行完毕后将控制权交回caller,callee可以用return或者抛出Exception来结束自己的执行。 多个执行上下文会形成执行上下文栈,最顶层是当前执行上下文,底层是全局执行上下文。
作用域(scope chain)是每一个执行上下文自身持有的活动对象的集合,如在本执行上下文中声明的变量和函数以及方法参数传入的对象。 每一个执行上下文可以访问的对象包括自身的作用域和父执行上下文的作用域和父父执行上下文作用域直到全局作用域,这就产生了作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。
文章引用: JS 执行上下文栈 / 作用域链
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(); 作用域链一般会沿着自己当前的作用域查找变量,找不到会逐层向上查找,父级作用域链找不到会向全局作用域找,知道找到为止。作用域分全局作用域和局部作用域,全局作用域定义的变量会再局部作用域中访问到,而局部定义的变量由于作用域只能在局部作用域中访问
执行上下文 & 栈
执行上下文主要分为全局上下文、函数上下文和eval上下文。 代码执行时,首先会将全局上下文压入栈中,也就是全局上下文栈,全局上下文栈是唯一的。 执行函数时,会将函数压入栈中,形成函数上下文栈。函数的每次调用,都会产生一个新的上下文,包括自身调用。
作用域链
每一个作用域与当前上下文有关,它包括了当前上下文的定义的变量,函数,以及传入的参数。 作用域分为全局作用域和函数作用域,全局作用域的属性和变量会挂载在window中。在全局作用域中,无法访问函数作用域内部的变量,但是在函数内部可以访问全局作用域变量。 由于在函数内部可以访问其父级作用域的变量,这样,就产生了作用域链,也就是当我们查找某个变量时,如果当前作用域没有,那么就会向上查找(父级作用域->父父级作用域),直到全局作用域。
**** 1. js的上下文栈
听说过堆栈以及上下文执行环境 首次听说上下文栈
浏览器执行javascript代码是首先会创建一个执行环境 js的执行环境姑且看做两种吧
一种是全局执行环境 另一种是函数执行环境 全局执行环境有且只有一个都在window范围内 函数 执行环境可以有多个
执行环境都存放在栈中 什么是栈 ?
我的理解是 代码执行时开辟出来的一部分内存空间 将全局执行环境和函数执行环境依次存放于此空间 ,代码执行时 ,首先执行全局上下文 ,若全局上下文 ,有包括函数上下文 那么按上面列为大佬的说法就是 caller 会把访问权交给 callee 待callee执行完毕 访问权会重新归还caller 继续执行全局上下文 以此 待所有的函数执行环境执行完毕 那么全局执行环境最终执行完毕 此时 全局执行环境出栈 内存销毁。
**** 2. 作用域链
作用域链的概念一般存在于函数中 ,意味当在函数内部去访问某变量 且在当前函数内部 无法访问到 则会去当前函数的父级作用域访问 若父级作用域依旧找不到 那么一次向上访问 知道最后在window环境也无法找到 那么访问变量的值即为undefined
作用域链的查找只能向上 而不能向下。
运行机制
执行上下文
每段js 都有一个执行环环境 也就是常说的执行上下文
执行上下文 又分文 全局执行上下文 和 函数执行上下文
全局上下文 也就是最外层的执行环境 代码一开始就会先进入 执行的
函数上下文 当代码执行到一个函数体的时候 就进入了函数执行的上下文
执行上下文记录了代码运行时的环境,当前运行状态下 有且只有一个执行上下文起作用,问题执行上下文记录了什么了什么那? 也就是我们所说的词法环境 变量环境 来个例子看下 var x =1; function foo(){ var y =2; function a(){ var z =3; } a() } foo()
执行栈(调用栈)
代码运行时 首先会进入全局上下文 然后会执行到foot()时 就进入了 foo的上下文 ,然后代码继续执行 执行到a() 此时进入了 a的上下文 执行完a()后 又回到了 foo 上下文 执行文foo() 后 又回到了 全局上下文, 所以在执行过程中会行程一个执行栈 也叫 调用栈 他是先进后出
作用域链
作用域是一个函数在执行时期的执行环境,每个执行环境都会有一个变量对象,用来存储这个环境中声明的变量或者函数 在各个执行环境之间的这个对象会行程查找关系 也就是所说的作用域链
JS上下文栈:一个javascript程序中,必定会产生多个执行上下文,javascript引擎会以栈的方式来处理它们,也就是执行上下文栈 作用域链:JS引擎中,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链。 函数作用域是在函数声明的时候就已经确定了,而函数执行上下文是在函数调用时创建的 执行上下文的生命周期: 创建阶段 生成变量对象(Variable object, VO) 建立作用域链(Scope chain) 确定this指向 执行阶段 变量赋值 函数引用 执行其他代码 ----写这么多吧,回头根据小姐姐的文章再总结下--
上下是函数执行时产生的,es5中只有函数作用域,并没有块作用域,es6中let,const让变量有了块作用域
执行上下文栈
是由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放于顶端
https://juejin.im/post/58eaecdea0bb9f0069271861 这篇文讲述了底层的原理, JS上下文栈:一个javascript程序中,必定会产生多个执行上下文,javascript引擎会以栈的方式来处理它们,也就是执行上下文栈 作用域链:JS引擎中,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链。 这是很通俗易懂的,但是如果理解其本质的内容的话,需要多加练习
JS是一门单线程语言,从上往下执行的。但是代码并不是一句一句的往下面执行,而是一段一段代码分析执行,首先会经历编译阶段,接着才是执行阶段。
执行上下文
执行上下文总共有三种类型
- 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
- 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
- Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用。
执行上下文栈
因为JS引擎创建了很多的执行上下文,所以JS引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。
当 JavaScript 初始化的时候会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,执行栈才会被清空,所以程序结束之前, 执行栈最底部永远有个 globalContext。
作用域链
当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链。
- https://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/
- http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。 JavaScript中的运行环境大概包括三种情况。
- 全局环境:JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
在一个JavaScript程序中,必定会产生多个执行上下文,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
- https://juejin.im/post/5ba32171f265da0ab719a6d7
执行上下文栈
简单的来说就是当我们在执行函数时,js引擎会为我们创建一个执行的上下文, 执行函数(入栈),函数结束(出栈),这个出栈入栈的过程就叫执行上下文 栈
。一旦代码执行就会创建一个全局的执行上下文,也就最先会入一个全局执行上下文栈
,每调用一个新的函数就会入一个新的执行上下文栈,一直到函数结束,它们一次动栈顶出栈,直到回到全局上下文栈
作用域链
说作用域链不得不说作用域
,作用域就是一个独立的空间,ES6增加了块级作用域的概念,在这之前只有全局作用域和函数作用域
作用域链就是自由变量(当前没有定义的变量,要往上级找)一层一层往上找,直到找到全局变量为止
var a = 100
function f1() {
var b = 200
function f2() {
var c = 300
console.log(a) // 自由变量,顺作用域链向父作用域找
console.log(c) // 本作用域的变量
}
f2()
}
f1()
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文类型分为:
- 全局执行上下文
- 函数执行上下文
- eval函数执行上下文(不被推荐)
作用域
作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。—— 摘录自《你不知道的JavaScript》(上卷)
作用域有两种工作模型:词法作用域和动态作用域,JS采用的是词法作用域工作模型,词法作用域意味着作用域是由书写代码时变量和函数声明的位置决定的。(with
和 eval
能够修改词法作用域,但是不推荐使用,对此不做特别说明)
作用域分为:
- 全局作用域
- 函数作用域
- 块级作用域
不要再说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()();