执行上下文作用域链
执行上下文(Execution Context):函数执行前进行的准备工作(也称执行上下文环境)
运行 JavaScript 代码的时候,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在运行代码前做一些准备工作,如确定作用域,创建局部变量对象等。
一:JavaScript执行环境
<script>
//全部变量
function foo(){
//局部上下文
console.log("12");
}
foo();//12
//eval函数:将字符串以js方式来运行
//eval函数上下文
eval("let a=1;console.log(a)"); //1
</script>
1.全局上下文
- 全局执行上下文
2.局部环境
- 局部执行上下文
3.eval函数环境
- eval函数:将字符串以js方式来运行
- eval函数执行上下文
- 如果参数是表达式,则 eval() 计算表达式。如果参数是一个或多个 JavaScript 语句,则 eval() 执行这些语句。
二:函数调用栈
在 JavaScript 中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。
三:执行上下文生命周期
- 执行上下文创建时会产生作用域链对象
- 作用域链对象包含变量对象(vo)和scope对象
- 其中Scope对象不是在执行上下文创建时产生,而是在函数声明时就产生了
- Scope保存的是声明该函数所在的执行上下文的作用域链对象
1.创建阶段
- 创建变量
- 初始化作用域链
- 确定this指向
- 确定作用域
2.执行阶段
- 变量对象赋值
- 调用函数
- 顺序执行其他代码
四:执行上下文和作用域的区别
前面有提到,执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除(除了闭包),处于活动状态的执行上下文环境只有一个。
而作用域在函数定义时就已经确定了,不是在函数调用时确定(区别于执行上下文环境,当然 this 也是上下文环境里的成分)
作用域只是一个"地盘",其中没有变量。变量是通过作用域对应的执行上下文环境中的变量对象来实现的。所以作用域是静态观念的,而执行上下文环境是动态上的,两者并不一样。
五:变量对象
在函数的建立阶段,首先会建立 Arguments 对象。然后确定形式参数,检查当前上下文中的函数声明,每找到一个函数声明,就在 variableObject 下面用函数名建立一个属性,属性值就指向该函数在内存中的地址的一个引用。如果上述函数名已经存在于 variableObject(简称VO) 下面,那么对应的属性值会被新的引用给覆盖。最后,是确定当前上下文中的局部变量,如果遇到和函数名同名的变量,则会忽略该变量。
六:作用域链
所谓作用域链,就是内部上下文所有变量对象(包括父变量对象)的列表。此链主要是用于变量查询。
scope:作用域链对象(声明对象的时候就开始产生scope了)
var a=50;
function foo1(){
console.log(`foo1:${a}`); //undefined
var a = 100;
}
foo1();
console.log(`全局:${a}`); //50
//当函数里面的var a = 100;改为a=100时,会输出(foo1:50,全局:100
var food="鸡翅";
function eat(){
console.log("吃",food);//吃鸡翅
}
(function(){
var food = "包子";
eat();
}())
谁声明的函数就执行谁的作用域链
var food = "鸡翅";
(function () {
var food = "包子";
function eat() {
console.log("吃", food);//吃包子
}
eat();
}())
var a = 1;
function b() {
console.log(a);
a = 10; //a与函数名重名,把栈里面的存放一个地址的a改为10(a是一个函数)
console.log(a);
return;
function a() {
}
}
b();
alert(a); //1
var f = true;
if (f === true) {
var a = 10;
}
function fun() {
var b = 20; //var在函数里面无法提升变量到函数外
c = 30; //非严格模式下,如果作用域中没有所查询变量,全局作用域就会自动创建一个具有该名称的变量
}
fun();
console.log(a);
console.log(b);
console.log(c);