由浅入深理解JS作用域

发布于:2024-04-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

前言

想要深入理解JavaScript语言,作用域是必需掌握的前置知识!作用域在JavaScript中扮演着至关重要的角色,它决定了变量的可见性和生命周期,直接影响着代码的行为和执行结果。通过深入理解作用域,开发者可以更加清晰地把握代码的逻辑结构,避免变量命名冲突和不必要的错误,提高代码的可维护性和可读性。

例题引入

Example One

var globalVar = 10;

function outerFunction() {
    var outerVar = 20;

    function innerFunction() {
        var innerVar = 30;
        console.log(innerVar);   // 输出:30
        console.log(outerVar);   // 输出:20
        console.log(globalVar);  // 输出:10
    }

    innerFunction();
}

outerFunction();

Example two

var globalVar = 10;

function outerFunction() {
    let outerVar = 20;

    if (true) {
        let blockVar = 30;
        console.log(blockVar);  // 输出:30
        console.log(outerVar);  // 输出:20
        console.log(globalVar); // 输出:10
    }

     console.log(blockVar); // 这里会报错,因为blockVar在代码块外不可见
}

outerFunction();

Example three

function outerFunction() {
    var outerVar = 10;

    function innerFunction() {
        var innerVar = 20;

        function closureFunction() {
            console.log(innerVar);   // 输出:20
            console.log(outerVar);   // 输出:10
        }

        return closureFunction;
    }

    return innerFunction();
}

var closure = outerFunction();
closure();

作用域

上面的例子引入,你或许会疑问为什么会输出这样的结果,下面让我们正式介绍一下作用域!

在 JavaScript 中,作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问。三种主要类型的作用域:全局作用域 函数作用域 块级作用域

全局作用域

  • 解释: 全局作用域是整个程序的最外层作用域,其中定义的变量可以在程序的任何地方访问。
  • 访问规则: 在全局作用域中声明的变量可以在程序的任何地方访问,包括函数内部和代码块内部。

函数作用域

  • 解释: 函数作用域是在函数内部声明的变量所处的作用域,变量的作用范围限定在函数内部。
  • 访问规则: 在函数内部声明的变量可以在函数内部任何地方访问,但不能在函数外部访问。函数外部的变量不能在函数内部直接访问,除非通过参数传递或闭包的形式

块级作用域

  • 解释: 块级作用域是通过花括号 {} 创建的作用域,例如在条件语句、循环语句和函数内部的代码块中。
  • 访问规则: 在块级作用域中声明的变量只能在当前块内部访问,超出当前块的范围则无法访问。这意味着在 if 语句、for 循环、while 循环或者函数内部声明的变量只在相应的代码块内部可见。

总结:内层作用域可以访问外层作用域中声明的变量,但外层作用域不能直接访问内层作用域中声明的变量。

欺骗词法作用域

在 JavaScript 中,词法作用域是由代码中变量声明的位置决定的。但是,有一些特殊的情况可以干扰词法作用域的正常行为,这就是欺骗词法作用域。主要的欺骗词法作用域的技术包括:

eval 函数eval 函数可以接受一个字符串作为参数,并将其当作 JavaScript 代码来执行。

使用 eval 函数

function evalExample() {
    var localVar = 10;
    eval('var evalVar = 20;');
    console.log(evalVar); // 输出:20,eval 函数在当前作用域声明了一个新变量
}

evalExample();

不使用 eval 函数

function noEvalExample() {
    var localVar = 10;
    console.log(evalVar); // 输出:ReferenceError: evalVar is not defined
}

noEvalExample();

with 语句with 语句用于修改一个对象中的属性值,但如果修改的属性在原对象中不存在,那么该属性会泄漏到全局

var obj = { x: 10 };

with (obj) {
    x = 20; // 修改对象中已存在的属性值
    y = 30; // 添加新的属性到对象中
}

console.log(obj.x); // 输出:20,对象中的 x 属性被修改
console.log(y); // 输出:30,变量y泄漏至全局

作用域链

作用域链(Scope Chain)是在 JavaScript 中用于查找变量的一种机制,它定义了在特定执行上下文中查找变量时的顺序和范围。

在 JavaScript 中,当需要访问一个变量时,JavaScript 引擎首先在当前执行上下文的变量对象中查找,如果找不到,则会沿着作用域链向上逐级查找,直到找到变量或者到达全局作用域。这个查找过程就是作用域链的工作原理,它决定了变量的可见性和访问范围。

先了解这些

.[[scope]]

作用域属性 给js引擎访问的

我们拿不到---隐式属性

GO(Global Object)

  • 概念: GO 是全局执行上下文的一部分,是全局环境中的对象。在浏览器环境中,GO 是 window 对象;在 Node.js 环境中,GO 是 global 对象。
  • 作用: GO 包含了全局范围内可访问的属性和方法,它是 JavaScript 程序的顶层对象。

AO(Activation Object)

  • 概念: 每次执行函数时,都会创建一个称为“执行环境(Execution Context)”的内部对象。其中的一个属性就是 AO,也叫作活动对象,它存储了当前执行上下文中的所有局部变量、函数声明和形参信息。
  • 作用: AO 是函数执行上下文的一部分,用于存储当前函数执行过程中的变量、参数和函数声明等信息。

通过例子讲解

function a() {
  function b() {
    var b = 22;
    console.log(glob);
  }
  var a = 111;
  b();
}

var glob = 100;
a();

画图分析作用域链:

image.png

分析作用域链的工作过程:

  1. 当调用函数 a() 时,会创建一个名为 a 的变量,其值为 111,并且会创建函数 b()
  2. 在函数 b() 中,我们声明了一个名为 b 的局部变量,其值为 22
  3. 在函数 b() 的内部,我们试图访问全局变量 glob
  4. JavaScript 引擎首先在函数 b() 的作用域中查找变量 glob,由于没有在函数 b() 的作用域中找到,因此会沿着作用域链向上查找。
  5. JavaScript 引擎进入函数 a() 的作用域,继续查找变量 glob。在函数 a() 的作用域中也没有找到变量 glob
  6. JavaScript 引擎继续沿着作用域链向上查找,最终在全局作用域中找到了变量 glob,其值为 100
  7. 最后,JavaScript 引擎将找到的全局变量 glob 的值 100 输出到控制台。

变量声明关键字

通过上述对作用域的了解,我们再补充一个变量声明关键字的知识!

在JavaScript中,变量声明的关键字主要有var, let, 和 const。下面是它们的区别:

关键字 变量作用域 声明提升 可重新赋值 可重复声明
var 函数作用域/全局作用域
let 块级作用域
const 块级作用域
  • var 与 let const声明在全局作用域的区别:

在JavaScript中,letconst 也可以在全局作用域中声明变量,但是它们和 var 的行为略有不同。

在全局作用域中使用 var 声明的变量会成为全局对象的属性,例如在浏览器中,全局对象是 window。而使用 letconst 声明的变量不会成为全局对象的属性,它们仅在当前的全局执行上下文中存在。

举个例子,在浏览器环境中:

var globalVar = 10;
let globalLet = 20;
const globalConst = 30;

console.log(window.globalVar); // 10
console.log(window.globalLet); // undefined
console.log(window.globalConst); // undefined

在这个例子中,globalVarwindow 对象的属性,而 globalLetglobalConst 不是。这是因为 letconst 声明的变量不会被添加到全局对象中。

  • 声明提升

    • 在 JavaScript 中,使用 var 声明的变量存在声明提升(hoisting)的现象,即在代码执行前就已经被声明了,但是在声明前使用它会得到 undefined
    • 使用 letconst 声明的变量不会出现变量提升的情况,这意味着在声明之前访问变量会导致 ReferenceError。
  • 可重新赋值

    • 使用 varlet 声明的变量都可以重新赋值。
    • 使用 const 声明的变量一旦被赋值后就不能再重新赋值。(针对基本数据类型,复杂数据类型另外讨论)
  • 可重复声明

    • 在同一个作用域中,使用 var 可以多次声明同一个变量名,后续的声明会覆盖之前的声明。(根本原因就是因为var可以声明提升)
    • 使用 letconst 声明的变量不允许在同一作用域中重复声明同一个变量名,否则会导致语法错误。

最后

如果觉得小编的总结有所帮助得话,请"一键三连"吧,有问题也可以在评论区提出!

关注我,我将输出更多优质内容!

可以再了解了解这篇文章:


网站公告

今日签到

点亮在社区的每一天
去签到