声明提升
变量和函数声明的提升(Hoisting):
-
变量声明:使用var声明的变量会被提升到当前作用域的顶部,但变量的初始化(赋值)保留在原地。这意味着在变量声明语句执行前,变量已经存在但其值为undefined。
-
函数声明:使用function关键字声明的函数也会被提升到作用域的顶部,这意味着在整个作用域中,函数可以在声明之前被调用。
函数中的预编译
编译器的操作步骤:
-
创建函数的执行上下文对象(AO对象 Activation Object)
-
会找形参和变量声明,再将形参和变量名作为AO的属性,值为undefined。变量声明(var)会被提升至作用域顶部,但变量初始化不会。
-
将实参和形参统一
-
在函数体内找函数声明,将函数名作为AO的属性名,值被赋予给函数体。如果一个函数和一个变量名冲突,函数声明会覆盖变量声明。 例如:
function fn(a) { console.log(a); var a = 123; console.log(a); function a() {} // 函数声明,声明一个叫做a的函数体 console.log(a); var b = function(){} // 函数表达式 console.log(b) function d(){} var d = a; console.log(d); } fn(1);- 执行前编译器创建一个AO对象
- 在函数体里面找形参和变量声明,将它们作为ao的属性名,值为undefined AO: { a: undefined, b: undefined, d: undefined, }
- 将实参和形参统一 AO: { a: undefined, -->1 b: undefined, d: undefined, }
- 在函数体内找函数声明,将函数名作为AO的属性名,值被赋予给函数体 AO: { a: 1, --> function a(){} b: undefined, d: undefined, --> function d(){} }
- 执行代码
-
第二行的console.log(a)的输出是[Function: a] -
第三行将123赋值给a -
第四行console.log(a)的输出是 123 -
第五行是函数声明,不用管 -
第六行console.log(a)的输出是 123 -
第七行先声明了一个变量b,再将匿名函数赋值给变量b -
第八行的console.log(b)输出的是[Function: b] -
第九行是函数声明,不用管 -
第十行是将a的值赋值给d,此时a的值为123,所以此时d的值为123 -
最终的输出为:第十一行的console.log(d)输出的是123
123
123
[Function: b]
123
全局的预编译
1. 创建全局执行上下文对象(GO)
2. 找变量声明,变量名作为GO的属性名,值为undefined
3. 在全局找函数声明,函数名作为GO的属性名,值为函数体Function
例如:
```var global = 100
function fn(){
console.log(global);
}
fn();
```
-
创建全局执行上下文对象(GO)
-
找变量声明,变量名作为GO的属性名,值为undefined
-
在全局找函数声明,函数名作为GO的属性名,值为函数体Function
GO: { global: undefined fn: function fn() {} }
4. 执行代码
此时
```
AO: {
// 无变量声明和函数声明
}
```
当在一个函数内部访问一个变量时,它会首先在当前函数的作用域(AO)中查找该变量。如果在当前作用域找不到,就会向上一层作用域查找,这个过程一直持续到全局作用域。在这个例子中,fn 函数内部没有声明自己的 global 变量,因此它会向上访问全局作用域中的 global 变量,值为 100。
调用栈
调用栈(Call Stack),也常被称为函数调用栈,是编程语言中一种核心的执行模型概念,用于追踪和管理函数的调用流程。在JavaScript中,这一概念尤为重要,因为JavaScript是基于函数调用和执行上下文管理的。
具体来说,调用栈是一种数据结构,通常实现为栈(Last-In-First-Out, LIFO)的形式,它记录了函数调用的顺序和状态。当一个JavaScript函数被调用时,会做以下几件事情:
1.创建执行上下文:引擎会为该函数创建一个新的执行上下文,这个上下文包含了函数执行所需的所有信息,比如局部变量、参数、this的值以及对外部变量的引用。
2.压栈:新创建的执行上下文会被推(Push)到调用栈的顶部。这意味着每次函数调用都会在栈顶添加一个新的层次,代表当前正在执行的函数。
3.执行函数:函数开始执行其代码块。
4.弹栈:当函数执行完毕并返回时,其对应的执行上下文会从调用栈中弹出(Pop),控制权返回到调用该函数的上一层函数或者全局作用域。
如果函数内部又调用了其他函数,则这个过程会递归进行,形成一个函数调用的层级结构。调用栈能够帮助我们理解和追踪代码的执行流程,特别是在调试时,通过查看调用栈可以了解函数是如何被一步步调用的,以及当前执行到哪一层级。
当调用栈过深,即调用的函数太多以至于超过了引擎允许的最大调用栈大小时,会抛出“栈溢出”(Stack Overflow)错误。
例如:
function add(){
var b = 10;
return a+b;
}
add();
感谢阅读!