var 关键字:JavaScript 变量声明的起点

💡 学习目标:掌握 var 的基本用法、作用域规则和提升机制,为理解 let 和 const 打下基础

🎯 什么是 var?

var 是 JavaScript 中最早的变量声明方式,它可以存储任意类型的数据。想象它就像一个万能的容器,你可以往里面放数字、文字、甚至复杂的对象。

基础语法

// 声明但不赋值
var message;  // 默认值为 undefined
console.log(message); // undefined
 
// 声明并赋值
var greeting = 'Hello, World!';
console.log(greeting); // "Hello, World!"
 
// 可以随时改变值和类型
var data = 'text';    // 字符串
data = 42;            // 数字
data = true;          // 布尔值
data = ['a', 'b'];    // 数组
console.log(data);    // ['a', 'b']

🌟 一次声明多个变量

// 传统方式(不推荐)
var name = 'Alice';
var age = 25;
var isActive = true;
 
// 优雅方式(推荐)
var name = 'Alice',
    age = 25,
    isActive = true;
 
// 混合声明
var x = 10,
    y,           // undefined
    z = x * 2;   // 20

🎯 var 的作用域规则

函数作用域 vs 全局作用域

var 具有函数作用域,这意味着在函数内声明的变量只在该函数内可见。

// ✅ 正常的函数作用域
function calculateTotal() {
    var price = 100;        // 函数内的局部变量
    var tax = price * 0.1;  // 只在函数内可见
    return price + tax;
}
 
console.log(calculateTotal()); // 110
// console.log(price);         // ❌ ReferenceError: price is not defined

🚨 全局变量的陷阱

// ❌ 危险:意外创建全局变量
function createUser() {
    username = 'john';  // 忘记写 var!
    var email = 'john@example.com';
}
 
creatеUser();
console.log(username); // "john" - 糟糕!全局变量被创建了
// console.log(email);  // ❌ ReferenceError

为什么这很危险?

// 全局变量污染示例
function moduleA() {
    counter = 0;  // 意外的全局变量
}
 
function moduleB() {
    counter = 100; // 修改了同一个全局变量!
}
 
moduleA();
console.log(counter); // 0
moduleB();
console.log(counter); // 100 - moduleA 的数据被破坏了!

✅ 严格模式的保护

'use strict';
 
function safeFunction() {
    // message = 'hi';  // ❌ ReferenceError in strict mode
    var message = 'hi';  // ✅ 正确的方式
}

🌍 块级作用域的缺失

与其他编程语言不同,var 没有块级作用域

function example() {
    console.log('开始函数');
    
    if (true) {
        var blockVar = '我在 if 块里';
    }
    
    for (var i = 0; i < 3; i++) {
        var loopVar = '我在 for 循环里';
    }
    
    // 🤔 这些变量在函数的任何地方都能访问
    console.log(blockVar); // "我在 if 块里"
    console.log(loopVar);  // "我在 for 循环里"
    console.log(i);        // 3 (循环结束后仍然存在)
}
 
example();

🚁 var 的提升机制(Hoisting)

什么是提升?

提升(Hoisting)是 JavaScript 的一个重要概念,指变量和函数声明在编译阶段被移动到作用域顶部的行为。

// 🤔 这段代码为什么不报错?
function mysteriousFunction() {
    console.log('用户名:', username);  // undefined (而不是 ReferenceError)
    console.log('年龄:', age);         // undefined
    
    var username = 'Alice';
    var age = 25;
    
    console.log('用户名:', username);  // "Alice"
    console.log('年龄:', age);         // 25
}
 
mysteriousFunction();

🔍 提升的原理

JavaScript 引擎实际上是这样处理的:

// 你写的代码
function example() {
    console.log(message);  // undefined
    var message = 'Hello';
    console.log(message);  // "Hello"
}
 
// JavaScript 引擎看到的代码(概念上)
function example() {
    var message;           // 声明被提升到顶部
    console.log(message);  // undefined
    message = 'Hello';     // 赋值留在原地
    console.log(message);  // "Hello"
}

📚 更复杂的提升示例

function complexHoisting() {
    console.log('1. x =', x);  // undefined
    console.log('2. y =', y);  // undefined
    
    if (false) {  // 注意:这个代码块不会执行
        var x = 1;
    }
    
    for (var i = 0; i < 1; i++) {
        var y = 2;
    }
    
    console.log('3. x =', x);  // undefined (因为 if 块没执行)
    console.log('4. y =', y);  // 2
    console.log('5. i =', i);  // 1
}
 
complexHoisting();
 
// 等价于:
function complexHoisting() {
    var x, y, i;              // 所有声明都被提升
    
    console.log('1. x =', x); // undefined
    console.log('2. y =', y); // undefined
    
    if (false) {
        x = 1;                // 只有赋值留在原地
    }
    
    for (i = 0; i < 1; i++) {
        y = 2;
    }
    
    console.log('3. x =', x); // undefined
    console.log('4. y =', y); // 2
    console.log('5. i =', i); // 1
}

🔄 重复声明的处理

function multipleDeclarations() {
    var name = 'First';
    console.log('1.', name);  // "First"
    
    var name = 'Second';      // 不会报错
    console.log('2.', name);  // "Second"
    
    var name;                 // 声明但不赋值
    console.log('3.', name);  // "Second" (值不变)
    
    var name = 'Third';
    console.log('4.', name);  // "Third"
}
 
multipleDeclarations();

⚠️ 提升的常见陷阱

// 陷阱 1: 意外的 undefined
function trap1() {
    if (shouldInitialize()) {
        console.log('初始化 config');
        var config = loadConfig();
    }
    
    return config;  // undefined!即使 if 条件为 false
}
 
function shouldInitialize() { return false; }
function loadConfig() { return {theme: 'dark'}; }
 
console.log(trap1()); // undefined
 
// 陷阱 2: 循环中的闭包问题
function trap2() {
    var callbacks = [];
    
    for (var i = 0; i < 3; i++) {
        callbacks.push(function() {
            console.log('回调', i);  // 所有回调都输出 3!
        });
    }
    
    callbacks.forEach(cb => cb());
}
 
trap2(); // "回调 3" "回调 3" "回调 3"

💡 最佳实践

// ✅ 推荐:在函数顶部声明所有 var 变量
function goodPractice() {
    var name, age, isActive;  // 集中声明
    
    name = 'Alice';
    age = 25;
    isActive = true;
    
    // 函数逻辑...
    return { name, age, isActive };
}
 
// ✅ 更好:使用 let 和 const(现代方式)
function modernPractice() {
    const name = 'Alice';
    let age = 25;
    let isActive = true;
    
    // 没有提升问题,更清晰的作用域
    return { name, age, isActive };
}

🎓 实战练习

练习 1:预测输出

// 请预测下面代码的输出
function quiz1() {
    console.log('a:', a);
    console.log('b:', b);
    console.log('c:', c);
    
    var a = 1;
    var b = 2;
    var c = 3;
    
    console.log('a:', a);
    console.log('b:', b);
    console.log('c:', c);
}
 
quiz1();
📝 答案
a: undefined
b: undefined
c: undefined
a: 1
b: 2
c: 3

练习 2:修复问题

// 这段代码有问题,请修复它
function createCounters() {
    var counters = [];
    
    for (var i = 0; i < 3; i++) {
        counters.push(function() {
            return i;
        });
    }
    
    return counters;
}
 
const counters = createCounters();
console.log(counters[0]()); // 应该输出 0,但实际输出 3
console.log(counters[1]()); // 应该输出 1,但实际输出 3
console.log(counters[2]()); // 应该输出 2,但实际输出 3
💡 解决方案
// 方案 1:使用立即执行函数(IIFE)
function createCounters() {
    var counters = [];
    
    for (var i = 0; i < 3; i++) {
        counters.push((function(index) {
            return function() {
                return index;
            };
        })(i));
    }
    
    return counters;
}
 
// 方案 2:使用 let(推荐)
function createCounters() {
    var counters = [];
    
    for (let i = 0; i < 3; i++) {
        counters.push(function() {
            return i;
        });
    }
    
    return counters;
}

🤔 思考题

  1. 为什么 JavaScript 要设计变量提升机制?
  2. 在什么情况下,你仍然会选择使用 var 而不是 letconst
  3. 如何向新手开发者解释变量提升的概念?

📖 延伸阅读:变量提升是理解 JavaScript 执行机制的关键,它关系到作用域、闭包等重要概念。现代开发中虽然更推荐使用 letconst,但理解 var 的特性对维护老代码和深入理解语言本质仍然很重要。

下一篇: let 声明