执行上下文
执行上下文:当前代码的执行环境,变量或者函数的上下文决定了它们可以访问哪些数据,以及它们的行为
执行上下文可以分为:
- 全局执行上下文:只有一个,程序首次运行时创建,它会在浏览器中创建一个全局对象(
window
对象),使this
指向这个全局对象
- 函数执行上下文:函数被调用时创建,每次调用都会为该函数创建一个新的执行上下文
- Eval 函数执行上下文:运行
eval
函数中的代码时创建的执行上下文,少用且不建议使用
执行上下文会在其所有代码执行完毕后被销毁,包括定义在它上面的所有变量和函数
执行上下文栈
执行上下文栈是一种拥有后进先出的数据解构,用于存储代码执行时创建的执行上下文
代码实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var color = 'blue';
function changeColor() { var anotherColor = 'red'; function swapColors() { var tempColor = anotherColor; anotherColor = color; color = tempColor; } swapColors(); }
changeColor();
console.log(color);
|
执行过程可以在 devTool
的 call stack
中看到,其中 anonyomus
为全局上下文栈;其余为函数上下文栈
图解:
执行过程:
- 首先创建了
全局执行上下文
,压入执行栈,其中的可执行代码开始执行。
- 然后调用
changeColor
函数,JS引擎停止执行全局执行上下文,激活函数 changeColor
创建它自己的执行上下文,且把该函数上下文放入执行上下文栈顶,其中的可执行代码开始执行。
changeColor
调用了 swapColors
函数,此时暂停了 changeColor
的执行上下文,创建了 swapColors
函数的新执行上下文,且把该函数执行上下文放入执行上下文栈顶。
- 当
swapColors
函数执行完后,其执行上下文从栈顶出栈,回到了 changeColor
执行上下文中继续执行。
changeColor
没有可执行代码,也没有再遇到其他执行上下文了,将其执行上下文从栈顶出栈,回到了 全局执行上下文
中继续执行。
- 一旦所有代码执行完毕,JS引擎将从当前栈中移除
全局执行上下文
。
1
| 注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。
|
变量对象
每个执行上下文都有一个关联的变量对象(variable object),这个执行上下文中定义的所有变量和函数都存在于这个变量上
如果执行上下文是函数,则其活动对象(activation object)作为变量对象,活动对象最初只有一个定义变量arguments
作用域
作用域由函数或代码块创建,变量只能在定义它的函数或代码块内使用,超出范围则不可访问,作用域决定了代码区块中变量和其他资源的可见性
作用域可分为:
全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域
- 最外层函数 和 在最外层函数外面 定义的变量拥有全局作用域
- 所有未定义直接复制的变量自动声明为拥有全局作用域
- 所有 window 对象的属性拥有全局作用域
函数作用域
函数作用域顾名思义就是指声明在函数内部的变量,函数作用域一般只在固定的代码片段内可以访问到
1 2 3 4 5 6 7 8 9 10 11
| function sayName() { const name = 'Katrina'; function innerSay() { console.log(name); } innerSay(); };
sayName(); console.log(name); innerSay();
|
块级作用域
块级作用域可以通过let和const声明,所声明的变量在指定块级作用域外无法被访问,会将变量的作用域限制在当前代码块中
块级作用域的特点详见:变量 | var let const区别及应用
词法作用域
词法作用域是定义表达式并能被访问到的区间,即一个声明的词法作用域就是它被定义时所在的作用域
1 2 3 4 5 6 7 8 9 10 11 12 13
| const name = 'Katrina';
function sayName() { return name; };
console.log(sayName());
|
作用域链
在当前作用域没有找到所需变量,就去父级寻找,父级没有找到再一层层向上找,直到全局作用域还没找到就放弃,这种一层层的关系就是作用域链
作用域与执行上下文
JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:
解释阶段:
执行阶段:
JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的
执行上下文最明显的就是 this 的指向是执行时确定的,而作用域访问的变量是编写代码的结构确定的
作用域和执行上下文之间最大的区别是:
执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变
一个作用域下可能包含若干个上下文环境,有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)
同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值
参考
你真的了解执行上下文吗?
掌握JavaScript面试:什么是闭包?
深入理解 JavaScript 作用域和作用域链