JavaScript 变量、常量与声明:存储数据的基石
在程序的世界里,我们需要一个地方来存放各种数据——就像在现实生活中,我们用便签纸记录待办事项,用笔记本记录想法,用标签标记物品。在 JavaScript 中,变量和常量就扮演着这样的角色,它们是存储数据的容器。
什么是变量?
变量(Variable)就像是一个贴了标签的盒子,你可以在盒子里放东西,也可以随时更换盒子里的内容。标签是变量名,盒子里的内容是变量值。
// 创建一个变量就像准备一个盒子
let userName = "Emily";
// 可以查看盒子里的内容
console.log(userName); // "Emily"
// 可以更换盒子里的内容
userName = "David";
console.log(userName); // "David"三种声明方式:var、let、const
JavaScript 提供了三种声明变量的关键字,它们就像三种不同类型的容器,各有特点。
var——旧时代的遗留物
var 是 JavaScript 最早的变量声明方式,在 ES6(2015 年)之前,它是唯一的选择。但由于存在一些问题,现在已经不推荐使用了。
// 使用 var 声明变量
var age = 25;
var city = "New York";
var isStudent = true;
console.log(age, city, isStudent); // 25 "New York" true
// var 可以重复声明同名变量(这是个问题!)
var age = 30; // 不会报错,但容易引起混乱
console.log(age); // 30var 的主要问题:
- 函数作用域:声明的变量只在函数内有效,没有块级作用域
- 变量提升:声明会被提升到作用域顶部,可能导致混乱
- 可重复声明:同名变量可以被重复声明,容易出错
// 问题示例 1:没有块级作用域
if (true) {
var message = "Hello";
}
console.log(message); // "Hello" - 变量泄露到外部!
for (var i = 0; i < 3; i++) {
// 循环体
}
console.log(i); // 3 - 循环变量泄露!
// 问题示例 2:变量提升
console.log(fruit); // undefined - 不会报错,但结果令人困惑
var fruit = "apple";
// 上面的代码实际上被解释为:
var fruit;
console.log(fruit);
fruit = "apple";let——现代的变量声明
let 是 ES6 引入的新关键字,它解决了 var 的大部分问题。就像给盒子加了明确的使用范围,让管理更加规范。
// 使用 let 声明变量
let userName = "Alice";
let userAge = 28;
let isActive = true;
// 可以修改 let 声明的变量
userName = "Bob";
userAge = 29;
isActive = false;
console.log(userName, userAge, isActive); // "Bob" 29 falselet 的优势:
- 块级作用域:变量只在声明的代码块内有效
- 不能重复声明:同一作用域内不能声明同名变量
- 暂时性死区:在声明前不能使用变量
// 优势 1:块级作用域
if (true) {
let message = "Hello";
console.log(message); // "Hello"
}
console.log(message); // ReferenceError: message is not defined
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ReferenceError: i is not defined
// 优势 2:不能重复声明
let score = 100;
let score = 200; // SyntaxError: Identifier 'score' has already been declared
// 优势 3:暂时性死区(Temporal Dead Zone,TDZ)
console.log(color); // ReferenceError: Cannot access 'color' before initialization
let color = "blue";const——常量的守护者
const 也是 ES6 引入的,用于声明常量。就像用强力胶封死的盒子,一旦放进东西,就不能再更换了。
// 使用 const 声明常量
const PI = 3.14159;
const API_URL = "https://api.example.com";
const MAX_USERS = 100;
// 尝试修改 const 变量会报错
PI = 3.14; // TypeError: Assignment to constant variable
console.log(PI); // 3.14159const 的特点:
- 必须初始化:声明时必须赋值
- 不能重新赋值:值不能被改变
- 块级作用域:与 let 相同
- 对象属性可变:如果是对象,对象的属性仍可修改
// 特点 1:必须初始化
const name; // SyntaxError: Missing initializer in const declaration
const name = "John"; // 正确
// 特点 2:不能重新赋值
const age = 25;
age = 26; // TypeError: Assignment to constant variable
// 特点 4:对象属性可变(重要!)
const user = {
name: "Sarah",
age: 25
};
// 不能重新赋值整个对象
user = { name: "Tom", age: 30 }; // TypeError
// 但可以修改对象的属性
user.age = 26;
user.city = "London";
console.log(user); // { name: "Sarah", age: 26, city: "London" }
// 同样适用于数组
const numbers = [1, 2, 3];
numbers.push(4); // 可以修改数组内容
numbers[0] = 100; // 可以修改元素
console.log(numbers); // [100, 2, 3, 4]
numbers = [5, 6, 7]; // TypeError: Assignment to constant variable如何使用 Object.freeze 创建真正的常量
如果你想让对象完全不可变,可以使用 Object.freeze():
const config = Object.freeze({
apiUrl: "https://api.example.com",
timeout: 5000,
});
// 尝试修改会静默失败(非严格模式)或报错(严格模式)
config.apiUrl = "https://new-api.example.com";
config.newProp = "value";
console.log(config);
// { apiUrl: "https://api.example.com", timeout: 5000 }
// 没有任何改变
// 严格模式下会报错
("use strict");
const settings = Object.freeze({ theme: "dark" });
settings.theme = "light"; // TypeError: Cannot assign to read only property变量命名规则
给变量起名字就像给宠物起名字,需要遵循一些规则,同时也有一些约定俗成的最佳实践。
必须遵守的规则
// 1. 只能包含字母、数字、下划线($)和美元符号($)
let userName = "valid";
let user_name = "valid";
let $user = "valid";
let _user = "valid";
// 2. 不能以数字开头
let 1user = "invalid"; // SyntaxError
let user1 = "valid";
// 3. 不能使用 JavaScript 保留字
let let = "invalid"; // SyntaxError
let const = "invalid"; // SyntaxError
let function = "invalid"; // SyntaxError
let if = "invalid"; // SyntaxError
// 4. 区分大小写
let name = "John";
let Name = "Sarah";
let NAME = "Michael";
// 这是三个不同的变量!最佳实践
// 1. 使用驼峰命名法(camelCase)
let userName = "John";
let userAge = 25;
let isActive = true;
let getUserInfo = function () {};
// 2. 常量使用大写+下划线
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = "https://api.example.com";
const DEFAULT_TIMEOUT = 5000;
// 3. 使用有意义的名称
// ❌ 不好的命名
let x = "John";
let d = new Date();
let arr = [1, 2, 3];
// ✅ 好的命名
let userName = "John";
let currentDate = new Date();
let userScores = [1, 2, 3];
// 4. 布尔值用 is/has/can 等前缀
let isLoggedIn = true;
let hasPermission = false;
let canEdit = true;
// 5. 对于私有变量,可以用下划线前缀(约定)
let _privateVar = "private";
let _internalCounter = 0;作用域理解
作用域(Scope)决定了变量的可访问范围,就像房子里的不同房间有不同的权限范围。
全局作用域
在任何函数或代码块之外声明的变量具有全局作用域,可以在代码的任何地方访问。
// 全局作用域
let globalVar = "I am global";
const GLOBAL_CONST = 100;
function test() {
console.log(globalVar); // 可以访问
console.log(GLOBAL_CONST); // 可以访问
}
if (true) {
console.log(globalVar); // 可以访问
}
test();函数作用域
在函数内部声明的变量只能在函数内部访问。
function myFunction() {
let localVar = "I am local";
console.log(localVar); // 可以访问
}
myFunction();
console.log(localVar); // ReferenceError: localVar is not defined块级作用域
用 let 和 const 声明的变量具有块级作用域,只在声明它们的代码块(用 {} 包围的区域)内有效。
if (true) {
let blockVar = "I am in a block";
const BLOCK_CONST = 200;
console.log(blockVar); // 可以访问
console.log(BLOCK_CONST); // 可以访问
}
console.log(blockVar); // ReferenceError
console.log(BLOCK_CONST); // ReferenceError
// 循环中的块级作用域
for (let i = 0; i < 3; i++) {
// 每次循环 i 都是新的变量
setTimeout(() => console.log(i), 100);
}
// 输出: 0, 1, 2
// 对比 var 的行为
for (var j = 0; j < 3; j++) {
// 所有回调共享同一个 j
setTimeout(() => console.log(j), 100);
}
// 输出: 3, 3, 3变量提升(Hoisting)
变量提升是 JavaScript 的一个特性,变量和函数声明会被移动到作用域的顶部。理解这个概念很重要,虽然现代代码很少依赖它。
var 的提升
console.log(myVar); // undefined
var myVar = "Hello";
// 上面的代码实际执行顺序:
var myVar; // 声明被提升
console.log(myVar); // undefined
myVar = "Hello"; // 赋值留在原处let 和 const 的提升
let 和 const 也会提升,但不会初始化,导致"暂时性死区"。
console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = "Hello";
// 暂时性死区(TDZ)
let x = 1;
{
// 这个块中,x 在声明前不可用
console.log(x); // ReferenceError
let x = 2; // 这里的 x 是新变量,遮蔽了外部的 x
}选择使用哪种声明方式
这是一个实际开发中的重要决策,以下是推荐的选择策略:
// 1. 默认使用 const
const userName = "Alex";
const userAge = 30;
const config = { theme: "dark" };
// 2. 只有当变量需要重新赋值时才使用 let
let counter = 0;
counter++;
counter++;
let currentPage = 1;
currentPage = 2;
// 3. 避免使用 var(除非需要兼容老代码)
// 不推荐
var oldStyleVar = "avoid this";
// 实际示例:循环
// ✅ 推荐
for (let i = 0; i < 10; i++) {
console.log(i);
}
// ❌ 不推荐
for (var j = 0; j < 10; j++) {
console.log(j);
}
// 实际示例:配置对象
// ✅ 推荐
const appConfig = {
name: "MyApp",
version: "1.0.0",
};
// 可以修改属性
appConfig.version = "1.0.1";
// 但不能重新赋值
// appConfig = {}; // TypeError声明但不赋值
变量可以先声明,后赋值。
// let 可以声明后再赋值
let userName;
console.log(userName); // undefined
userName = "Jessica";
console.log(userName); // "Jessica"
// const 必须在声明时赋值
const userAge; // SyntaxError: Missing initializer in const declaration
// 正确做法
const userAge = 25;多变量声明
可以在一条语句中声明多个变量。
// 分别声明(推荐,更清晰)
let firstName = "Tom";
let lastName = "Smith";
let age = 35;
// 一起声明(紧凑但可读性稍差)
let x = 1,
y = 2,
z = 3;
// 混合声明不同类型
let name = "Laura",
score = 95,
isPassed = true,
grade; // 可以不初始化
// const 也可以
const PI = 3.14,
E = 2.718,
GOLDEN_RATIO = 1.618;常见问题与解决方案
问题 1:const 对象属性可以修改吗?
答案:可以修改属性,但不能重新赋值整个对象。
const person = {
name: "Anna",
age: 28,
};
// ✅ 可以修改属性
person.age = 29;
person.city = "Paris";
// ❌ 不能重新赋值
person = { name: "Bob" }; // TypeError
// 如果需要完全冻结对象
const frozenPerson = Object.freeze({
name: "Charlie",
age: 40,
});
frozenPerson.age = 41; // 静默失败(非严格模式)
console.log(frozenPerson.age); // 仍然是 40问题 2:为什么循环中推荐用 let?
答案:let 在每次循环中创建新的绑定,而 var 共享同一个变量。
// 使用 let(正确)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 0, 1, 2
}, 1000);
}
// 使用 var(问题)
for (var j = 0; j < 3; j++) {
setTimeout(() => {
console.log(j); // 输出 3, 3, 3
}, 1000);
}
// var 的问题解决方案(使用闭包)
for (var k = 0; k < 3; k++) {
(function (index) {
setTimeout(() => {
console.log(index); // 输出 0, 1, 2
}, 1000);
})(k);
}问题 3:未声明的变量会怎样?
答案:直接使用未声明的变量会创建全局变量(非严格模式)或报错(严格模式)。
// 非严格模式
function test() {
undeclaredVar = "Oops!"; // 意外创建全局变量
}
test();
console.log(undeclaredVar); // "Oops!" - 全局污染!
// 严格模式(推荐)
("use strict");
function strictTest() {
undeclaredVar = "Error!"; // ReferenceError: undeclaredVar is not defined
}总结
变量和常量是 JavaScript 编程的基础,选择正确的声明方式能让代码更安全、更易维护。
本节要点回顾:
- var:函数作用域,有变量提升,不推荐使用
- let:块级作用域,可重新赋值,现代开发的首选
- const:块级作用域,不可重新赋值,用于常量和不变的引用
- 最佳实践:默认使用 const,需要改变时使用 let,避免 var
- 命名规则:使用有意义的名称,遵循驼峰命名法
- 作用域:理解全局、函数和块级作用域的区别
- const 声明的对象属性可以修改,但对象引用不能改变