2. JavaScript 变量在不同作用域下的使用场景

0 阅读7分钟

JavaScript 变量在不同作用域下的使用场景?

关键词:配置项、快速测试、隐藏变量、闭包、模块化、嵌套块、条件语句、循环变量、隔离逻辑

Keywords: Configuration items, quick testing, hidden variables, closures, modularization, nested blocks, conditional statements, loop variables, isolated logic

当我们知道了 JavaScript 的作用域分为 全局作用域、函数作用域 和 块级作用域,那么我们又该如何正确使用它们呢?也就是这些作用域的使用场景分别有哪些?

How to use JavaScript scopes correctly? -> What are the usage scenarios of these scopes?

下面来分别介绍变量在这三类作用域中的使用场景。

image.png

1 全局作用域的使用场景-Global Scope Usage Scenarios

全局作用域在 JavaScript 中应该 谨慎使用,但在某些场景下它也具有其独特的优势和必要性。

The global scope should be uesd with caution in JavaScript, but it also has its unique advantages and necessary in certain scenarios.

1.1 共享状态-Shared State

1.1.1 添加配置项-Adding Configuration Items

在全局作用域中声明的常量,可以在整个应用程序中被共享和访问。比如添加配置项。

Constants declared in the global scope can be schared and accessed throughout the javascript application. For example, adding configuration items.

const API_URL = "https://api.example.com";
const MAX_RETRIES = 3;

function fetchData() {
    // 使用全局常量
    console.log(`Fetching data from ${API_URL}...`);
}
1.1.2 在多个函数、模块间,访问或操作同一变量

多个函数间多个模块间,存在访问或操作 同一变量 的情况,可在 全局作用域中 声明该变量,适应于 小型应用程序 中。

When the same variable is accessed or operated between multiple functions or multipel modules, the variable can be declared in the global scope, which is suitable for small applications.

let userCount = 0;

function incrementUserCount() {
    userCount++;
}

function getUserCount() {
    return userCount;
}
1.1.3 在回调函数中-In The Callback Function

将变量声明在全局作用域,回调函数中可以方便 访问和更新该全局变量的状态

Declare the variable in the global scope so that the status of the global variable can be easily accessed and updated in the callback function.

let isLoggedIn = false;

function toggleLogin() {
    isLoggedIn = !isLoggedIn; // 直接获取和更新全局变量的状态
    console.log(`User is now ${isLoggedIn ? "logged in" : "logged out"}`);
}

document.getElementById("loginButton").addEventListener("click", toggleLogin); // 函数 toggleLogin(回调函数) 作为参数传递给 点击事件

1.2 调试和开发阶段-Debugging And Development Phase

在调试和开发阶段,可以利用全局作用域 快速测试变量和函数,而无需考虑作用域的问题。

During debugging and development, you can use the global scope to quickly test variables and functions without having to worry about scope issues.

function debug() {
    console.log(globalVariable); // 可以直接访问
}

1.3 注意事项-Things To Note

但过度依赖全局变量可能导致命名冲突难以维护的代码,使用注意事项:

However, over-reliance on global variables may lead to naming conflicts and difficult-to-maintain code. Note:

命名规范,为全局变量使用独特的命名规则,以减少冲突的可能性。

Naming conventions: Use unique naming conventions for global variables to reduce the possibility of conflicts.

2 函数作用域的使用场景-Function Scope Usage Scenarios

理解和利用函数作用域 是编写高质量 JavaScript 代码的基础。它使得代码更加安全、更易维护,同时避免了许多常见错误。

Understanding and utilizing function scope is fundamental to writing high-quality JavaScript code. It makes your code safer and easier to maintain, while avoiding many common mistakes.

2.1 隐藏变量-Hidden Variables

将变量封装在函数内部,避免它们被外部代码访问。这有助于保护数据不被意外修改。

Encapsulate variables inside functions to prevent them from being accessed by external code. This helps protect data from being accidentally modified.

function createCounter() {
    let count = 0; // count 只在此函数内可见
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2

2.2 避免变量命名冲突-Avoid Variable Naming Conflicts

在函数内部声明的变量名称与外部(全局上一级作用域)声明的变量名称相同时,函数内部的变量会“遮蔽”外部的变量,即将优先访问函数作用域内的变量,而不是外部同名变量。

When the variable name declared inside a function is the same as the variable name declared outside (global or previous level scope), the variable inside the function will "schadow" the external variable, that as, the variable in the function scope will be accessed first instead of the external variable with the same name.

这种现象称为“作用域遮蔽”,从而减少命名冲突的风险。

This phenomenon is called "scope shadowing" and reduces the risk of naming conflicts.

let value = 5;

function example() {
    let value = 10; // 内部变量不会影响外部变量
    console.log(value); // 输出:10
    // 即优先访问该函数作用域的变量,外部的同名变量被遮蔽。
}

example();
console.log(value); // 5

当然,如果确实需要在该函数内部访问外部的同名变量,可以使用以下两种方法:

  • 如果外部是 全局作用域的变量,可通过 全局对象 window(或 globalThis) 来访问;
  • 如果外部是 嵌套函数中的变量,可通过 将外部变量作为参数传入嵌套函数。

2.3 创建闭包-Creating A Closure

闭包通常是一个“内部函数”。它的本质在于内部函数能够访问到外部函数的变量,即使在外部函数执行结束后,这些变量依然能够被访问到。

A closure is usually an "inner function". Its essence is that the inner function can access the variables of the outer function, and these variables can still be accessed even after the outer function is executed.

它利用这种持有外部变量的引用,从而保留了其执行上下文

It uses this to hold references to external variables, thereby preserving its execution context.

函数作用域是闭包的基础。这在处理异步操作事件处理时非常有用。

Function scope is the basis of closures. This is very useful when dealing with asynchronous operations and event handling.

function makeMultiplier(multiplier) {
    return function(x) {
        return x * multiplier; // 访问外部变量 multiplier
    };
}

const double = makeMultiplier(2);
console.log(double(5)); // 10

2.4 封装相关功能-Encapsulatiom Related Functions

函数作用域可以用来创建模块,保持代码的可维护性。通过相关的功能封装在函数中,可以实现更好的模块化设计。

Function scope can be used to create modules and keep the code maintainable. By encapsulating related functions in functions, better modular design can be achieved.

function mathModule() {
    function add(a, b) {
        return a + b;
    }
    function subtract(a, b) {
        return a - b;
    }
    return { add, subtract };
}

const math = mathModule();
console.log(math.add(5, 3)); // 8
console.log(math.subtract(5, 3)); // 2

3 块级作用域的使用场景-Block Scope Usage Scenarios

块级作用域提供了一种更细粒度的控制变量可见性的方式,主要通过 letconst 实现。

Block scope provides a more fine-grained way to control variable visibility, primary through let and const.

它可以有效减少命名冲突、封装变量、提高代码可读性和可维护性。

It can effectively reduce naming conflicts, encapsulate variables, and improve code readability and maintainability.

3.1 隐藏变量-Hidden Variables

用来封装变量,以创建私有状态,特别在模块模式中。

Used to encapsulate variables to create private state, especially in the module pattern.

function createCounter() {
    let count = 0; // count 只在这个函数内部可见
    return {
        increment() {
            count++;
            return count;
        },
        decrement() {
            count--;
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1

3.2 多个嵌套块中的相同变量名

多个嵌套块中 使用 相同的变量名 而不产生冲突,简化代码的管理。

Use the same variable name in mutiple nested blocks without conflicts, simplifying code management.

let name = "Global";

if (true) {
    let name = "Block";
    console.log(name); // "Block"
}

console.log(name); // "Global"

3.3 在循环语句中-In Loop Statements

在循环中使用 let 声明循环变量,确保每次迭代都有一个独立的变量避免闭包问题

Use let declare the loop variable in the loop to ensure that each iteration has an independent variable and avoid closure problems.

for (let i = 0; i < 5; i++) {
    // 异步操作,当循环结束 i=5,执行定时器函数时,i的值已经是5
    setTimeout(() => {
        console.log(i); // 输出 0, 1, 2, 3, 4
    }, 100);
}

3.4 在条件语句中-In Conditional Sataements

在条件语句中使用 letconst,确保变量只在特定条件下可见,避免意外修改。

Use let or const in conditional statements to ensure that variables are visible only under certain conditions and avoid accidental modifications.

if (true) {
    let blockVar = "I am block scoped!";
    console.log(blockVar); // 可访问
}
// console.log(blockVar); // 错误:blockVar 不在此作用域内

3.5 隔离函数内部逻辑-Isolate Function Internal Logic

复杂函数中使用块级作用域可以 将逻辑划分为不同的区域便于理解变量的作用范围,提高代码可读性。

Using block scope in complex functions can divide the logic into different areas and make it easier to understand the scope of variables, improve code readability.

function processData(data) {
    if (data) {
        let processed = data.map(item => item * 2);
        console.log(processed); // 处理后的数据
    }
    // console.log(processed); // 错误:processed 不在此作用域内
}

4 结尾-End

当我们知道了上面这些作用域的使用场景后,在编写代码的时候,就会有意识的去按照这些规则去使用它们。

When we know the usage scenarios of the above scopes, we will consciously use them according to these rules when writing code.

这样可以使我们编写出的代码在满足功能需求的同时,规避一些不易察觉的错误。

This allows us to write code that meets functional requirements while avoiding some slight errors.