Skip to content

变量与作用域

在 JavaScript 中,变量和作用域是核心概念,直接影响代码的执行逻辑和性能。以下是详细解析:

一、变量声明

JavaScript 提供了三种声明变量的方式:varletconst,它们在作用域、提升、可变性等方面有显著区别。

1. var

  • 函数作用域:声明的变量在函数内有效,函数外不可见。
  • 变量提升:声明会被提升到作用域顶部,初始化留在原地。
    javascript
    console.log(x); // undefined
    var x = 10;
  • 可重新赋值:允许重复声明和修改值。
    javascript
    var a = 5;
    var a = 10; // 合法

2. let

  • 块级作用域:声明的变量在块({})内有效。
  • 无变量提升:存在暂时性死区(TDZ),在声明前访问会报错。
    javascript
    console.log(y); // ReferenceError: y is not defined
    let y = 20;
  • 不可重复声明:同一作用域内不能重复声明。
    javascript
    let b = 30;
    let b = 40; // SyntaxError

3. const

  • 块级作用域:同 let
  • 常量声明:声明后不可重新赋值,但对象属性可以修改。
    javascript
    const PI = 3.14;
    PI = 3.1416; // TypeError: Assignment to constant variable
    const obj = { a: 1 };
    obj.a = 2; // 合法

二、作用域类型

1. 词法作用域(静态作用域)

  • 定义时确定:作用域由代码结构(函数嵌套)决定,而非执行时。
  • 示例
    javascript
    function outer() {
      let x = 10;
      function inner() {
        console.log(x); // 访问 outer 的 x
      }
      inner();
    }
    outer(); // 10

2. 动态作用域

  • 执行时确定:作用域由调用栈决定(JavaScript 不采用此机制)。

三、作用域链

  • 查找规则:当访问变量时,JavaScript 引擎会向上级作用域依次查找,直到全局作用域。
  • 示例
    javascript
    let globalVar = "global";
    function outer() {
      let outerVar = "outer";
      function inner() {
        let innerVar = "inner";
        console.log(globalVar); // 'global'
        console.log(outerVar); // 'outer'
        console.log(innerVar); // 'inner'
      }
      inner();
    }
    outer();

四、闭包

  • 定义:函数嵌套时,内部函数引用外部函数的变量,形成闭包。
  • 特性
    • 外部函数执行完毕后,变量不会被垃圾回收。
    • 可用于数据封装、私有变量等场景。
  • 示例
    javascript
    function createCounter() {
      let count = 0;
      return function () {
        count++;
        return count;
      };
    }
    const counter = createCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2

五、this 指向

this 的值在运行时动态绑定,取决于函数的调用方式。

调用方式this 指向
全局调用全局对象(浏览器中是 window
函数调用全局对象(严格模式下为 undefined
方法调用方法所属的对象
构造函数调用新创建的对象
箭头函数外层作用域的 this

示例

javascript
const obj = {
  name: "Alice",
  greet: function () {
    console.log(this.name); // 'Alice'
  },
};
obj.greet();

const arrowGreet = () => {
  console.log(this.name); // undefined(箭头函数继承外层 this)
};
arrowGreet();

六、内存管理

  • 变量生命周期
    • 声明时分配内存。
    • 不再使用时被垃圾回收(通过引用计数或标记清除算法)。
  • 内存泄漏场景
    • 闭包未释放引用。
    • 未清除的定时器或事件监听器。
    • 全局变量误用。

七、常见面试题

  1. varlet 的区别?

    • var 是函数作用域,会提升;let 是块级作用域,无提升。
  2. 闭包的优缺点?

    • 优点:数据封装、实现私有变量。
    • 缺点:可能导致内存泄漏。
  3. 如何解决循环中闭包的问题?

    • 使用立即执行函数(IIFE)或 let 声明循环变量。
      javascript
      for (let i = 0; i < 3; i++) {
        setTimeout(() => console.log(i), 1000); // 正确输出 0, 1, 2
      }
  4. 箭头函数与普通函数的 this 区别?

    • 箭头函数的 this 继承自外层作用域,普通函数的 this 动态绑定。

八、最佳实践

  1. 优先使用 letconst,避免 var
  2. 谨慎使用闭包,及时释放不再需要的引用。
  3. 合理设计作用域层次,避免变量污染。
  4. 使用严格模式('use strict')避免隐式全局变量。

理解变量和作用域是掌握 JavaScript 运行机制的关键,也是解决复杂问题的基础。

Released under the MIT License.