由浅入深的理解JavaScript 执行机制/执行上下文

变量提升

所谓的变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。在 JavaScript 中,使用 var 声明的变量会提升到其所在的作用域顶部。这意味着,无论在作用域中的哪个位置声明了变量,它都可以在该作用域中的任何位置访问

下面是一个例子来说明 var 变量提升的概念:

console.log(a); // undefined
var a = 1;
console.log(a); // 1

在这个例子中,我们在第一行尝试打印变量 a 的值,虽然这个变量在这行代码之前没有声明和赋值,但结果却是 undefined 而不是报错。这是因为 JavaScript 引擎在代码执行之前会先对 var 声明的变量进行提升,所以这段代码实际上等价于:

var a;
console.log(a); // undefined
a = 1;
console.log(a); // 1

在代码执行之前,变量 a 被提升到作用域的顶部,并被赋予了初始值 undefined。因此,尽管我们在第一行尝试打印变量 a 的值,它的值为 undefined。当代码执行到 var a = 1; 这一行时,变量 a 被赋予了新的值 1

需要注意的是,变量提升只会提升声明,而不会提升赋值。也就是说,只有变量名被提升到作用域顶部,而不是变量的实际值。所以在提升阶段之前,变量的值都是 undefined

总结一下,var 声明的变量会被提升到作用域顶部,但是赋值操作并不会提升,所以在变量被赋值之前,它的值是 undefined。因此,最佳实践是在使用变量之前先声明和赋值,以避免可能的意外行为。

至于为什么要变量提升,在于 JavaScript 代码在执行前需要先编译,编译时变量和函数会被放到变量环境中。

例如:

// 这是我们正常写的代码:
sayHello();
console.log(hello);
var hello = "你好";
function sayHello() {
  console.log("sayHello");
}
//模拟变量提升后的代码
var hello = undefined;
function sayHello() {
  console.log("sayHello");
}
​
sayHello();
console.log(hello);
hello = "你好";

函数和变量在执行前都提升到代码开头。而对于出现了同名的变量或者函数,最终生效的是最后一个(覆盖)

块级作用域

  • 作用域:是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性生命周期

  • 全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期

  • 函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁

块级作用域:就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。

ES6 引入了 letconst 关键字,从而使 JavaScript 拥有了块级作用域,能够解决一些变量提升带来的问题。

使用 let 关键字声明的变量是可以被改变的,而使用 const 声明的变量其值是不可以被改变的。

以下是变量提升和块级作用域的理解考察题:

function test() {
  const a = 1;
  const b = 2;
  {
    const b = 3;
    var c = 4;
    const d = 5;
    console.log(a);
    console.log(b);
  }
  console.log(b);
  console.log(c);
  console.log(d);
}
test();
  1. 现在我们逐行分析代码:

    1. const a = 1;const b = 2; 是在函数 foo 的变量环境中声明了常量 ab

    2. const b = 3; 是在 foo 函数内部的代码块中声明了一个新的常量 b,它的作用范围只在该代码块内部。

    3. var c = 4; 使用 var 关键字声明的变量 c 会提升到函数作用域顶部,并且能在整个函数范围内访问。

    4. const d = 5; 是在 foo 函数内部代码块中声明了一个新的常量 d,它的作用范围只在该代码块内部。

    5. console.log(a); 打印变量 a 的值,输出 1

    6. console.log(b); 打印变量 b 的值,输出 3,因为此处的 b 是在内部代码块中的新声明。

    7. console.log(b); 打印变量 b 的值,输出 2,因为此处的 b 是在函数范围内的变量。

    8. console.log(c); 打印变量 c 的值,输出 4,因为 c 是在函数范围内声明的变量。

    9. console.log(d); 打印变量 d 的值,引发 ReferenceError 错误,因为 d 是在代码块内部声明的变量,超出了它的作用范围。

综上最终的输出结果为:1、3、2、4、err

作用域链、闭包

作用域链:是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数。

闭包:在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 init,那么这些变量的集合就称为 init 函数的闭包。

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();

displayName() 没有自己的局部变量。然而,由于闭包的特性,它可以访问到外部函数的变量

使用场景
  • 在函数外部能够访问到函数内部的变量。

  • 使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值