深入理解作用域、变量提升、暂时性死区、执行上下文
2024-05-26 23:14:04
浏览:73
评论:0
在js代码执行过程中,我们会碰到很多概念,对此我们进行逐一梳理。
作用域
作用域是一个概念,用于描述变量和函数在代码中的可访问范围。它定义了变量和函数在不同作用域中的可见性和生命周期。 作用域的概念是在编译时确定的,并且在代码执行期间不会改变。
全局作用域
- 在浏览器环境中,全局作用域通常指的是window对象的作用域,也就是说,在脚本的最外层定义的变量会自动成为全局对象的属性。
- 在Node.js环境中,全局作用域有所不同,全局对象不是window而是global。
- 全局变量在整个程序的生命周期中都可访问,但过度使用全局变量可能会导致变量名冲突和数据污染问题。
// 使用预先解析的
func(); // 输出函数
// 后解析
var func = '变量';
// 会先解析
function func() {
console.log('函数');
}
// 后解析覆盖先解析
console.log(func); // 输出变量
函数作用域
- 局部作用域最常见的形式是函数作用域。在函数内部定义的变量只在该函数内部可见。
- 当函数执行完毕后,局部变量会被销毁,有助于内存管理。
- 这种机制有助于封装和避免变量污染全局作用域。
function func() {
// 函数作用域
var a = 10;
console.log(a);
}
// console.log(a); // ReferenceError: a is not defined
func();
// console.log(a); // ReferenceError: a is not defined
块级作用域
- 使用let和const关键字声明的变量具有块级作用域,这意味着它们只在定义它们的代码块(例如if语句、for循环或一对大括号内)中有效。
- var关键字声明的变量没有块级作用域,它遵循函数作用域或全局作用域。
- 块级作用域的引入增强了JavaScript对变量作用域的控制能力,减少了因变量提升(hoisting)导致的一些问题。
执行上下文
执行上下文是在代码执行期间创建的一个运行环境,它包含了变量对象、作用域链等信息。 执行上下文的创建和销毁是在代码执行过程中动态发生的。 执行上下文分为:全局执行上下文、函数执行上下文、eval执行上下文
全局执行上下文
- 形成时机:全局代码执行前
- 描述:
- 在 ( 全局代码 ) 执行前,将 ( window ) 确定为 ( 全局执行上下文对象 )
- 对 ( 全局数据 ) 进行 预处理
- 变量提升,函数提升,并将全局变量和函数变量确定为window的属性
- 将 this 指向 window
函数执行上下文
- 形成时机:函数调用时,并且在 ( 执行函数体之前 ),( 创建 ) 函数执行上下文对象
- 描述:
- 在 ( 函数调用,并且函数体执行前 ),新 ( 创建 ) ( 函数执行上下文对象 )
- 形参赋值为实参
- 变量提升,函数名提升
- arguments 对象赋值
- this 赋值为该函数执行上下文对象
eval执行上下文
- eval函数执行时产生的执行上下文,不建议使用
全局执行上下文 和 函数执行上下文 对比
- 全局执行上下文是把window确定为全局执行上下文,之前window就存在
- 函数执行上下文在函数调用并且函数体执行前,新创建的函数执行上下文对象
执行上下文栈(函数调用栈)
- 栈是一个后进先出的结构,用于储存 在js代码执行期间产生的所有执行上下文
- 具体过程:
- 在javascript刚开始运行时,首先产生全局执行上下文,压入栈,位于栈底
- 每当发生一个函数被调用,则产生函数执行上下文,压入栈,位于栈顶
- 当这个函数执行完后,会从执行上下文栈中弹出
- 引擎会继续去执行位于栈顶的函数
function v1() {
console.log('v1');
v2();
}
function v2() {
console.log('v2');
}
v1();
作用域链
上下文代码在执行的时候,会创建变量对象的一个作用域链,这个作用域链决定了各种上下文的代码在访问变量和函数时的顺序。 代码正在执行的上下文的变量对象,始终位于作用域链的最前端,如果上下文是函数,则其活动对象用作变量对象。
变量提升
变量提升(Hoisting)是JavaScript中一个特有的概念,涉及到变量和函数声明在代码执行前被解析并“提升”到其所在作用域的顶部的行为。然而,需要注意的是,虽然在逻辑上看似变量被提升到了作用域的顶部,但实际上这个过程发生在JavaScript引擎编译阶段,并不影响代码的实际物理顺序。
变量声明提升
使用var声明的变量会被提升,但变量的赋值操作不会。这意味着在变量声明之前的代码可以访问到变量,但此时变量的值为undefined。
console.log(x); // 输出 undefined
var x = 5;
函数声明提升
函数声明(function declaration)会被整体提升,包括函数名和函数体,所以在声明之前调用函数也是合法的。
foo(); // 正常调用
function foo() {
console.log("Hello");
}
暂时性死区
- 使用let和const(ES6引入)声明的变量不会被提升,如果在声明前访问它们,会导致ReferenceError。
- 与函数声明不同,函数表达式(function expression)不会被提升,只有变量声明会被提升,而函数定义保留在原地。