变量与作用域
在 JavaScript 中,变量和作用域是核心概念,直接影响代码的执行逻辑和性能。以下是详细解析:
一、变量声明
JavaScript 提供了三种声明变量的方式:var、let、const,它们在作用域、提升、可变性等方面有显著区别。
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();六、内存管理
- 变量生命周期:
- 声明时分配内存。
- 不再使用时被垃圾回收(通过引用计数或标记清除算法)。
- 内存泄漏场景:
- 闭包未释放引用。
- 未清除的定时器或事件监听器。
- 全局变量误用。
七、常见面试题
var和let的区别?var是函数作用域,会提升;let是块级作用域,无提升。
闭包的优缺点?
- 优点:数据封装、实现私有变量。
- 缺点:可能导致内存泄漏。
如何解决循环中闭包的问题?
- 使用立即执行函数(IIFE)或
let声明循环变量。javascriptfor (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1000); // 正确输出 0, 1, 2 }
- 使用立即执行函数(IIFE)或
箭头函数与普通函数的
this区别?- 箭头函数的
this继承自外层作用域,普通函数的this动态绑定。
- 箭头函数的
八、最佳实践
- 优先使用
let和const,避免var。 - 谨慎使用闭包,及时释放不再需要的引用。
- 合理设计作用域层次,避免变量污染。
- 使用严格模式(
'use strict')避免隐式全局变量。
理解变量和作用域是掌握 JavaScript 运行机制的关键,也是解决复杂问题的基础。