1. JavaScript中的字面量:不可变的固定值
字面量(Literal)是编程中最基础的概念之一。在JavaScript中,字面量指的是直接在代码中表示固定值的表示法。它们就像是我们日常生活中写在纸上的数字或文字一样,是直接呈现的、不可更改的原始值。
1.1 字面量的常见类型
JavaScript中有多种类型的字面量,每种都有其特定的表示方式:
-
数字字面量:直接表示数值
javascript复制42 // 整数 3.14 // 浮点数 0xFF // 十六进制 0b1010 // 二进制 -
字符串字面量:用单引号或双引号包裹的文本
javascript复制'Hello' "World" `Template literal` // ES6新增的模板字符串 -
布尔字面量:只有两个值
javascript复制true false -
数组字面量:用方括号表示
javascript复制[1, 2, 3] ['a', 'b', 'c'] -
对象字面量:用花括号表示
javascript复制{ name: 'John', age: 30 } -
正则表达式字面量:用斜杠包裹
javascript复制
/pattern/flags
注意:虽然字面量可以直接使用,但在实际开发中,我们很少直接使用它们。原因在于:
- 直接使用字面量会使代码难以维护
- 重复的字面量会导致代码冗余
- 字面量缺乏描述性,降低了代码可读性
1.2 字面量的不可变性
字面量的一个重要特性是不可变性(Immutability)。这意味着一旦创建,它们的值就不能被改变。例如:
javascript复制5 = 10; // 报错:Invalid left-hand side in assignment
这种不可变性是JavaScript语言设计的基础之一,它确保了基本值的稳定性和可预测性。
2. JavaScript变量:数据的容器
2.1 变量的本质与作用
变量是JavaScript中存储和操作数据的基本单元。从技术角度讲:
- 内存分配:当声明一个变量时,JavaScript引擎会在内存中分配一块空间
- 标识符绑定:变量名作为标识符,与这块内存空间绑定
- 值存储:内存空间存储具体的值(可以是字面量或其他引用)
从开发者的角度看,变量主要有以下作用:
- 数据存储:保存程序运行过程中需要使用的值
- 值复用:避免重复书写相同的字面量
- 值描述:通过有意义的变量名提高代码可读性
- 值修改:在程序运行过程中动态改变存储的值
2.2 变量的声明方式
JavaScript中有三种声明变量的方式:var、let和const。每种方式都有其特定的行为和适用场景。
2.2.1 var声明(ES5及之前)
var是ES5及之前版本中声明变量的唯一方式。它的特点包括:
- 函数作用域:变量在函数内部有效
- 变量提升:声明会被提升到作用域顶部
- 可重复声明:同一作用域内可多次声明同名变量
- 可重新赋值:变量值可以多次修改
javascript复制// 基本用法
var score = 90;
score = 95; // 重新赋值
// 变量提升示例
console.log(name); // 输出:undefined
var name = 'John';
// 函数作用域示例
function test() {
var x = 10;
if (true) {
var x = 20; // 同一个变量
console.log(x); // 20
}
console.log(x); // 20
}
注意事项:由于
var的变量提升和函数作用域特性,在现代JavaScript开发中,建议尽量避免使用var,改用let和const。
2.2.2 let声明(ES6+)
let是ES6引入的变量声明方式,解决了var的一些问题:
- 块级作用域:变量只在当前代码块内有效
- 无变量提升:必须先声明后使用
- 不可重复声明:同一作用域内不能重复声明
- 可重新赋值:变量值可以修改
javascript复制// 基本用法
let count = 0;
count = 1; // 重新赋值
// 块级作用域示例
function test() {
let x = 10;
if (true) {
let x = 20; // 不同的变量
console.log(x); // 20
}
console.log(x); // 10
}
// 暂时性死区示例
console.log(y); // 报错:Cannot access 'y' before initialization
let y = 5;
2.2.3 const声明(ES6+)
const也是ES6引入的,用于声明常量:
- 块级作用域:同
let - 必须初始化:声明时必须赋值
- 不可重新赋值:变量值不能改变(对于基本类型)
- 不可重复声明:同
let
javascript复制// 基本用法
const PI = 3.14159;
// 对象和数组的特殊情况
const person = { name: 'Alice' };
person.age = 30; // 允许,修改的是对象属性
person = {}; // 报错:Assignment to constant variable
const numbers = [1, 2, 3];
numbers.push(4); // 允许,修改的是数组内容
numbers = []; // 报错:Assignment to constant variable
重要提示:
const保证的是变量绑定的不变性,而不是值的不变性。对于对象和数组,内容是可以修改的,只是不能重新赋值。
2.3 变量声明的最佳实践
基于现代JavaScript开发经验,建议遵循以下变量声明原则:
- 默认使用
const:除非确定值需要改变,否则优先使用const - 需要重新赋值时用
let:当变量需要多次赋值时使用let - 避免使用
var:除非有特殊需求或维护旧代码 - 声明位置尽量靠近使用位置:提高代码可读性
- 一次声明一个变量:避免使用逗号分隔的多变量声明
javascript复制// 好的实践
const MAX_SIZE = 100;
let currentPage = 1;
// 不好的实践
var x = 5, y = 10, z = 15;
3. 变量命名规范与技巧
良好的变量命名是写出可维护代码的关键。以下是JavaScript变量命名的核心规范和建议:
3.1 基本命名规则
-
合法标识符:
- 可以包含字母、数字、下划线和美元符号
- 不能以数字开头
- 不能是保留关键字(如
let、class等)
-
大小写敏感:
javascript复制let name = 'John'; let Name = 'Doe'; // 不同的变量 -
命名风格:
- camelCase:变量和函数名(如
userName) - PascalCase:构造函数和类名(如
UserModel) - UPPER_CASE:常量(如
API_KEY)
- camelCase:变量和函数名(如
3.2 命名最佳实践
-
描述性命名:
javascript复制// 不好 let d = 10; // 好 let daysSinceLastLogin = 10; -
避免缩写:
javascript复制// 不好 let usrCnt = 0; // 好 let userCount = 0; -
布尔变量前缀:
javascript复制let isActive = true; let hasPermission = false; -
集合变量使用复数:
javascript复制let users = ['Alice', 'Bob']; -
避免单字符命名:
javascript复制// 例外情况:循环计数器 for (let i = 0; i < 10; i++) { // ... }
3.3 特殊命名场景
-
私有变量:
- 传统约定:以下划线开头(
_privateVar) - 现代方案:使用ES6类私有字段(
#privateField)
- 传统约定:以下划线开头(
-
全局变量:
- 尽量避免使用全局变量
- 如果必须使用,考虑使用命名空间
-
临时变量:
javascript复制// 临时存储计算结果 let tempResult = calculateSomething();
4. 变量作用域与生命周期
理解变量的作用域和生命周期对于编写可靠的JavaScript代码至关重要。
4.1 作用域类型
-
全局作用域:
- 在任何函数和代码块之外声明的变量
- 在整个程序中都可以访问
- 容易造成命名污染,应尽量避免
javascript复制let globalVar = 'I am global'; function test() { console.log(globalVar); // 可以访问 } -
函数作用域:
- 使用
var声明的变量具有函数作用域 - 在整个函数内部都可以访问
javascript复制function test() { if (true) { var x = 10; } console.log(x); // 可以访问,输出10 } - 使用
-
块级作用域:
- 使用
let和const声明的变量具有块级作用域 - 只在声明它的代码块(
{})内有效
javascript复制function test() { if (true) { let y = 20; console.log(y); // 可以访问,输出20 } console.log(y); // 报错:y is not defined } - 使用
4.2 变量生命周期
-
创建阶段:
- 变量声明被提升(
var)或进入暂时性死区(let/const) - 内存空间被分配
- 变量声明被提升(
-
初始化阶段:
- 变量被赋值
- 对于
var,初始值为undefined - 对于
let/const,在声明语句执行前不可访问
-
使用阶段:
- 变量可以被读取和修改(除非是
const声明的基本类型)
- 变量可以被读取和修改(除非是
-
销毁阶段:
- 当变量离开其作用域时
- 内存可能被垃圾回收机制回收
4.3 作用域链与变量查找
JavaScript使用词法作用域(静态作用域),变量查找遵循以下规则:
- 首先在当前作用域查找
- 如果找不到,向上一级作用域查找
- 直到全局作用域
- 如果全局作用域也没有,则报错
javascript复制let globalVar = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(innerVar); // 'inner'
console.log(outerVar); // 'outer'
console.log(globalVar); // 'global'
}
inner();
}
outer();
5. 常见问题与解决方案
5.1 变量提升的陷阱
问题:
javascript复制console.log(hoistedVar); // undefined
var hoistedVar = 'value';
console.log(notHoisted); // 报错
let notHoisted = 'value';
解决方案:
- 使用
let和const代替var - 遵循"先声明后使用"的原则
- 使用ESLint等工具检测变量提升问题
5.2 块级作用域的误解
问题:
javascript复制for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出3次3
}
解决方案:
javascript复制// 使用let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出0,1,2
}
// 或使用闭包
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
5.3 常量对象的属性修改
问题:
javascript复制const obj = { prop: 'value' };
obj.prop = 'new value'; // 允许
obj = {}; // 报错
解决方案:
- 使用
Object.freeze()冻结对象javascript复制const obj = Object.freeze({ prop: 'value' }); obj.prop = 'new value'; // 静默失败或严格模式下报错 - 使用TypeScript的
readonly修饰符 - 文档明确说明哪些属性可以修改
5.4 全局变量污染
问题:
javascript复制// script1.js
var data = 'important data';
// script2.js
var data = 'oops, overwritten';
解决方案:
- 使用模块系统(ES Modules/CommonJS)
- 使用IIFE创建私有作用域
javascript复制(function() { var privateData = 'safe'; // ... })(); - 使用命名空间
javascript复制var MyApp = MyApp || {}; MyApp.data = 'value';
5.5 变量命名冲突
问题:
javascript复制function processData(data) {
let data = transform(data); // 报错:Identifier 'data' has already been declared
}
解决方案:
- 使用不同的变量名
javascript复制function processData(inputData) { let transformedData = transform(inputData); } - 分步骤处理数据
javascript复制function processData(data) { let result = transform(data); }
6. 性能考量与优化
虽然现代JavaScript引擎已经高度优化,但了解变量相关的性能特性仍然有助于编写更高效的代码。
6.1 变量访问速度
-
作用域链查找:
- 局部变量访问最快
- 全局变量访问较慢(需要遍历作用域链)
- 嵌套越深,访问越慢
-
优化建议:
javascript复制// 不好的做法:多次访问全局变量 function calculate() { return window.width * window.height * window.depth; } // 好的做法:缓存全局变量 function calculate() { const { width, height, depth } = window; return width * height * depth; }
6.2 内存管理
-
变量释放:
- 变量离开作用域后,其内存可以被回收
- 全局变量会一直存在,直到页面卸载
-
闭包注意事项:
javascript复制function createClosure() { let largeData = new Array(1000000).fill('data'); return function() { return largeData.length; }; } const closure = createClosure(); // largeData不会被释放 -
优化建议:
- 及时解除不再需要的引用
- 避免不必要的闭包
- 对于大型临时数据,使用后手动设置为
null
6.3 变量声明风格对性能的影响
-
varvslet/const:- 现代引擎对
let/const的处理已经高度优化 - 性能差异可以忽略不计
- 应基于语义而非性能选择声明方式
- 现代引擎对
-
批量声明:
javascript复制// 性能上没有显著差异 let a = 1, b = 2, c = 3; // 与 let a = 1; let b = 2; let c = 3; -
循环中的变量声明:
javascript复制// 每次迭代都会创建新的块级作用域变量 for (let i = 0; i < 1000; i++) { // ... } // 性能影响可以忽略,优先保证代码正确性
在实际项目中,变量相关的性能优化通常不是首要考虑因素。代码的可读性、可维护性和正确性应该放在首位。只有在性能分析确实表明变量使用是瓶颈时,才需要进行针对性优化。