变量、作用域与内存
原始值与引用值
1. 原始值
原始值是最基本的数据,包括:undefined、null、boolean、number、string、symbol 和 bigint。
javascript
// 原始值示例
let str = "hello"; // string
let num = 100; // number
let bool = true; // boolean
let n = null; // null
let u = undefined; // undefined
let sym = Symbol(); // symbol
let bigInt = 9007199254740991n; // bigint
// 原始值的特点
let name1 = "John";
let name2 = name1; // 复制值
name1 = "Jane"; // name2仍然是"John"
2. 引用值
引用值是由多个值构成的对象,保存在内存中。
javascript
// 引用值示例
let obj1 = { name: "John" };
let obj2 = obj1; // 复制引用
obj1.name = "Jane"; // obj2.name 也变成 "Jane"
// 常见的引用类型
let array = [1, 2, 3]; // Array
let date = new Date(); // Date
let regexp = /\d+/g; // RegExp
let error = new Error(); // Error
执行上下文与作用域
1. 执行上下文类型
javascript
// 全局执行上下文
var globalVar = "全局变量";
// 函数执行上下文
function exampleFunction() {
var localVar = "局部变量";
console.log(localVar); // 可访问
console.log(globalVar); // 可访问全局变量
}
// eval执行上下文(不推荐使用)
eval("var evalVar = 42;");
2. 作用域链
javascript
let global = "全局变量";
function outer() {
let outerVar = "外层变量";
function inner() {
let innerVar = "内层变量";
// 作用域链查找顺序:inner -> outer -> global
console.log(innerVar); // 内层变量
console.log(outerVar); // 外层变量
console.log(global); // 全局变量
}
inner();
}
3. 块级作用域
javascript
// let 和 const 的块级作用域
{
let blockVar = "块级变量";
const BLOCK_CONST = "块级常量";
console.log(blockVar); // 可访问
console.log(BLOCK_CONST); // 可访问
}
// console.log(blockVar); // ReferenceError
// console.log(BLOCK_CONST); // ReferenceError
// if 块级作用域
if (true) {
let ifVar = "if内部变量";
}
// console.log(ifVar); // ReferenceError
// for 循环块级作用域
for (let i = 0; i < 3; i++) {
let forVar = "循环内变量";
}
// console.log(i); // ReferenceError
// console.log(forVar); // ReferenceError
变量声明
1. var 声明
javascript
// 变量提升
console.log(varVariable); // undefined
var varVariable = "var变量";
// 函数作用域
function varScope() {
var functionVar = "函数作用域";
}
// console.log(functionVar); // ReferenceError
// 可重复声明
var repeat = "first";
var repeat = "second"; // 允许
2. let 声明
javascript
// 不存在变量提升
// console.log(letVariable); // ReferenceError
let letVariable = "let变量";
// 块级作用域
{
let blockLet = "块级作用域";
}
// console.log(blockLet); // ReferenceError
// 不可重复声明
let unique = "first";
// let unique = "second"; // SyntaxError
3. const 声明
javascript
// 声明时必须初始化
const CONSTANT = "常量";
// const EMPTY; // SyntaxError
// 不能修改绑定
const PI = 3.14159;
// PI = 3.14; // TypeError
// 对象属性可以修改
const user = { name: "John" };
user.name = "Jane"; // 允许
// user = {}; // TypeError
内存管理
1. 内存生命周期
javascript
// 1. 内存分配
let person = { // 分配内存
name: "John",
age: 30
};
// 2. 内存使用
console.log(person.name); // 使用内存
// 3. 内存释放
person = null; // 解除引用
2. 垃圾回收机制
javascript
// 标记清除算法
function process() {
let local = {}; // 分配内存
// 函数结束后,local不可访问,将被回收
}
// 引用计数算法(循环引用问题)
function createCycle() {
let obj1 = {};
let obj2 = {};
obj1.ref = obj2; // obj1 引用 obj2
obj2.ref = obj1; // obj2 引用 obj1
obj1 = null; // 断开一个引用
obj2 = null; // 断开另一个引用
}
3. 内存泄漏
javascript
// 1. 意外的全局变量
function leak() {
leakedVar = "我会泄漏"; // 没有声明就使用
}
// 2. 闭包导致的泄漏
function createLeak() {
let largeData = new Array(1000000);
return function() {
console.log(largeData[0]); // largeData 一直被引用
};
}
// 3. 定时器导致的泄漏
function setTimer() {
let data = { /* 大量数据 */ };
setInterval(() => {
console.log(data); // data 无法被回收
}, 1000);
}
性能优化
1. 变量优化
javascript
// 使用解构赋值
const { name, age } = person;
// 使用对象属性简写
const x = 1, y = 2;
const point = { x, y };
// 使用展开运算符
const newArray = [...oldArray];
const newObject = { ...oldObject };
2. 内存优化
javascript
// 对象池模式
const objectPool = {
_pool: [],
acquire() {
return this._pool.pop() || {};
},
release(obj) {
this._pool.push(obj);
}
};
// 使用 WeakMap/WeakSet
const cache = new WeakMap();
let key = { id: 1 };
cache.set(key, "数据");
key = null; // 缓存的数据可以被回收
// 分批处理大数据
function processLargeData(items) {
const chunk = 1000;
const process = (start) => {
const end = Math.min(start + chunk, items.length);
// 处理 items.slice(start, end)
if (end < items.length) {
setTimeout(() => process(end), 0);
}
};
process(0);
}
最佳实践
- 优先使用
const
,其次是let
,避免使用var
- 及时释放不再使用的引用
- 避免意外的全局变量
- 注意闭包可能导致的内存问题
- 使用块级作用域限制变量的生命周期
- 对大数据集合进行分批处理
- 使用弱引用(WeakMap/WeakSet)存储可能被清理的对象
注意事项
- 理解原始值和引用值的区别
- 注意变量声明的作用域
- 警惕内存泄漏的常见原因
- 合理使用垃圾回收机制
练习题
- 说明原始值和引用值的区别,并举例说明。
- 解释变量提升的概念,以及为什么要避免使用 var。
- 描述闭包可能导致的内存问题,并给出解决方案。
- 实现一个对象池模式,用于优化频繁创建和销毁对象的场景。