- 原文地址:marian-caikovski.medium.com/what-is-cop…
- 原文标题:Strings are not copied in JavaScript
- 原文作者:Marian Čaikovski
Objects
大家知道当一个对象赋值给一个变量时,对象的值保存在内存中的某处,变量保存的其实是对象在内存中的位置的引用。如果这个引用赋值给另一个变量,这两个变量指向的是同一个对象。
// sample1.js\
let a={id:1};\
let b=a;\
b.id=2;\
console.log(a); // {id: 2}
Non-objects
JavaScript的数据类型可以分为两类:对象和原始类型。
对象是属性的集合。属性是对象内的变量。对象和原始类型的本质区别在于原始类型无法拥有属性。举例来说,如果尝试给字符串添加个属性,会报错。
// sample2.js
let str='a';
str.id=1; // TypeError: Cannot create property 'id' on string 'a'
//(译注:其实不会报错,但是id属性也没有赋值成功,不会报错的原因在于自动装箱机制)
字符串
字符串是特别有趣的类型,因为在任何web程序或文字内容中,它占了不部分的内存。
一些业内专家声称可以以此区分对象和字符串:把字符串变量赋值给另一个变量时,它的值会被复制并分配给目标变量。
与对象不同的是,字符串是不可变的。许多方法都是返回新的字符串,却没有一个方法可以改变已存在的字符串。 举个例子,你不能改变字符串的字串而不创建新的字符串。复制无法改变且占内存多的值是对计算机资源的浪费。
实验
我无法再现第一个示例因为字符串无法拥有属性。但是有其他的方式佐证字符串的赋值过程和对象类似。可以通过测量内存使用情况代替属性变化。为了测量内容占用我将使用我在之前的文章提到的新内存API。
当我使用makeString(sizeMB)函数创建一个1000万字符的字符串并赋值给变量a,网页会被分配大约10MB的内存。
let a, b;
a = makeString(10); // 内存使用增加10MB
如果把a 设为 null,页面将释放之前为该字符串分配的10MB内存,因为字符串值不再可达(没有变量引用它了)。
a = null; // 内存使用减少10MB
如果你打开示例页面 stringassignment.onrender.com/ 并点击例子3按钮,上述代码会执行,在每个语句之后测量内存使用大小。但是你得稍等一会因为是在垃圾回收之后才进行测量内存 , which by default does not happen often in so simple pages.内存使用情况的变化会逐渐输出在控制台并最终显示到图中:
如果在a 设为 null之前,a被赋值给变量 b,应该发生什么?
b = a;
a = null;
如果字符串被复制并赋值给b,内存使用会在b = a;之后再增加10MB,在a = null;之后会减少10MB。
相反,如果复制给b的是原始字符串的引用,那么在b = a;之后,内存使用不会有明显变化;将引用赋值给b不需要额外的多少字节。然后 a = null,不会导致垃圾收集器释放10MB内存因为b依然保存着字符串的引用。这10MB的内存会在 b = null;之后释放。
如果你打开示例页面 stringassignment.onrender.com/ 并点击例子4按钮,稍等一会, 你会看到否定字符串变量赋值时字符串被复制了的图。
结论
当你将一个字符串变量赋值给另一个变量,传递的是字符串的引用而不是它的值。字符串本身没有被复制,如果它是那样的,是对内存和CPU的浪费。
示例代码从github.com/marianc000/…下载。