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;
}
🤔 思考题
- 为什么 JavaScript 要设计变量提升机制?
- 在什么情况下,你仍然会选择使用
var
而不是let
或const
? - 如何向新手开发者解释变量提升的概念?
📖 延伸阅读:变量提升是理解 JavaScript 执行机制的关键,它关系到作用域、闭包等重要概念。现代开发中虽然更推荐使用
let
和const
,但理解var
的特性对维护老代码和深入理解语言本质仍然很重要。