JavaScript 中给变量赋值
在JavaScript开发中,变量赋值是一个看似简单却容易让人误解的概念。很多开发者(包括曾经的我)可能会简单地认为所有变量赋值都是"复制"过程,或者都是"引用"过程。实际上,JavaScript中的变量赋值行为取决于数据类型。本文将详细解析JavaScript中不同数据类型的赋值机制,并通过代码示例和图表帮助你彻底理解这一重要概念。
内存模型图解
为了更直观地理解这些概念,让我们用Mermaid图表展示内存中的情况:
基本数据类型:复制过程
对于数字、字符串、布尔值等基本数据类型,JavaScript采用的是值复制的方式。
// 数字类型示例
var a = 3; // 声明变量a并赋值为3
var b = a; // 将a的值复制给b
b++; // b的值增加1
console.log(a); // 输出: 3 - a的值不受影响
console.log(b); // 输出: 4 - 只有b的值改变了// 字符串类型示例
var c = 'string'; // 声明变量c并赋值为'string'
var d = c; // 将c的值复制给d
c += ' is String'; // 修改c的值
console.log(c); // 输出: 'string is String' - c的值已改变
console.log(d); // 输出: 'string' - d的值保持不变
从上面的例子可以看出,对于基本数据类型,变量赋值确实是创建了一个独立的副本,修改其中一个变量不会影响另一个变量。
引用数据类型:地址引用
对于对象(Object)和数组(Array)这样的引用数据类型,情况就不同了。JavaScript采用的是地址引用的方式。
// 数组类型示例
var arr = [1, 2, 3]; // 声明数组arr
var brr = arr; // brr和arr引用同一个数组
brr.push(4); // 通过brr修改数组
console.log(arr); // 输出: [1, 2, 3, 4] - arr也被修改了
console.log(brr); // 输出: [1, 2, 3, 4] - 两个变量指向同一个数组// 对象类型示例
var obj = {a: 1, b: 2}; // 声明对象obj
var obj2 = obj; // obj2和obj引用同一个对象
obj.c = 3; // 通过obj添加新属性
console.log(obj); // 输出: {a: 1, b: 2, c: 3}
console.log(obj2); // 输出: {a: 1, b: 2, c: 3} - 两个变量指向同一个对象
这种情况下,变量实际上存储的是对内存中对象的引用(可以理解为地址),而不是对象本身的副本。因此,通过任何一个变量修改对象,都会影响到所有引用该对象的变量。
函数参数传递的特殊情况
当引用类型作为函数参数传递时,行为会有些微妙的变化:
var arr = [1, 2, 3]; // 原始数组// 情况1:修改参数内部属性
function change(a) {a.push(4); // 修改传入的数组return a;
}
var brr = change(arr);
console.log(arr); // 输出: [1, 2, 3, 4] - 原数组被修改
console.log(brr); // 输出: [1, 2, 3, 4]// 情况2:重新赋值参数
function set(a) {a = [3, 2, 1]; // 给参数赋新值,改变了引用地址return a;
}
var crr = set(arr);
console.log(arr); // 输出: [1, 2, 3, 4] - 原数组未被修改
console.log(crr); // 输出: [3, 2, 1] - 返回的是新数组
这里的关键区别在于:
- 如果只是修改参数内部的属性/元素,外部的变量会受到影响
- 如果对参数进行重新赋值,则外部的变量不会受到影响,因为参数现在引用的是一个新的对象
实践:
- 基本数据类型(Number, String, Boolean等)赋值是值复制,修改不会相互影响
- 引用数据类型(Object, Array等)赋值是地址引用,修改会相互影响
- 函数参数传递:
- 修改参数内部属性会影响外部变量
- 重新赋值参数不会影响外部变量
在实际开发中,为了避免意外的副作用,处理引用类型时可以考虑:
// 创建数组/对象的副本
var newArr = [...oldArr]; // 数组展开运算符
var newObj = {...oldObj}; // 对象展开运算符
var deepCopy = JSON.parse(JSON.stringify(complexObj)); // 深拷贝// 函数内避免修改外部对象
function safeModify(obj) {const localObj = {...obj}; // 创建副本localObj.property = 'new value';return localObj;
}