如何强制 JavaScript 深度复制一个字符串?

How to force JavaScript to deep copy a string?

我有一些 javascript 代码如下所示:

var myClass = {
  ids: {}
  myFunc: function(huge_string) {
     var id = huge_string.substr(0,2);
     ids[id] = true;
  }
}

稍后使用一些大字符串 (100 MB+) 调用该函数。我只想保存一个在每个字符串中找到的短 ID。但是,Google Chrome 的子字符串函数(在我的代码中实际上是正则表达式)只有 returns 一个 "sliced string" 对象,它引用了原始对象。因此,在对 myFunc 进行一系列调用后,我的 chrome 选项卡内存不足,因为无法对临时 huge_string 对象进行垃圾回收。

如何复制字符串 id 以便不维护对 huge_string 的引用,并且可以对 huge_string 进行垃圾回收?

JavaScript 的 ECMAScript 实现可能因浏览器而异,但是对于 Chrome,许多字符串操作(substr、slice、regex 等)只是保留对原始字符串的引用而不是复制字符串。这是 Chrome (Bug #2869) 中的一个已知问题。要强制复制字符串,以下代码有效:

var string_copy = (' ' + original_string).slice(1);

此代码通过在字符串前面附加一个 space 来工作。这种连接会在 Chrome 的实现中产生一个字符串副本。那么space后面的子串就可以引用了

此解决方案的问题已在此处重新创建:http://jsfiddle.net/ouvv4kbs/1/

警告:加载时间过长,打开 Chrome 调试控制台查看进度打印输出。

// We would expect this program to use ~1 MB of memory, however taking
// a Heap Snapshot will show that this program uses ~100 MB of memory.
// If the processed data size is increased to ~1 GB, the Chrome tab
// will crash due to running out of memory.

function randomString(length) {
  var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  var result = '';
  for (var i = 0; i < length; i++) {
    result +=
        alphabet[Math.round(Math.random() * (alphabet.length - 1))];
  }
  return result;
};

var substrings = [];
var extractSubstring = function(huge_string) {
  var substring = huge_string.substr(0, 100 * 1000 /* 100 KB */);
  // Uncommenting this line will force a copy of the string and allow
  // the unused memory to be garbage collected
  // substring = (' ' + substring).slice(1);
  substrings.push(substring);
};

// Process 100 MB of data, but only keep 1 MB.
for (var i =  0; i < 10; i++) {
  console.log(10 * (i + 1) + 'MB processed');
  var huge_string = randomString(10 * 1000 * 1000 /* 10 MB */);
  extractSubstring(huge_string);
}

// Do something which will keep a reference to substrings around and
// prevent it from being garbage collected.
setInterval(function() {
  var i = Math.round(Math.random() * (substrings.length - 1));
  document.body.innerHTML = substrings[i].substr(0, 10);
}, 2000);

我对字符串、对象、数组等使用Object.assign()方法:

const newStr = Object.assign("", myStr);
const newObj = Object.assign({}, myObj);
const newArr = Object.assign([], myArr);

请注意,Object.assign 仅复制对象内的键及其属性值(仅限一级)。深度克隆一个嵌套对象,参考下面的例子:

let obj100 = { a:0, b:{ c:0 } };
let obj200 = JSON.parse(JSON.stringify(obj100));
obj100.a = 99; obj100.b.c = 99; // No effect on obj200

我在推入数组时遇到问题。每个条目最终都会成为相同的字符串,因为它引用了一个对象上的值,该值在我通过 .next() 函数迭代结果时发生了变化。这是允许我复制字符串并在我的数组结果中获取唯一值的原因:

while (results.next()) {
  var locationName = String(results.name);
  myArray.push(locationName);
}

您可以使用:

 String.prototype.repeat(1) 

看起来效果不错。参考MDN documentation on repeat

不确定如何测试,但使用字符串插值创建新的字符串变量是否有效?

newString = `${oldString}`

我通常使用 strCopy = new String (originalStr); 出于某种原因不推荐这样做吗?

我 运行 遇到了这个问题,我是这样处理的:

let copy_string = [];
copy_string.splice(0, 0, str);

我相信这会将 str 深度复制到 copy_string。

使用 String.slice()

const str = 'The quick brown fox jumps over the lazy dog.';

// creates a new string without modifying the original string
const new_str = str.slice();

console.log( new_str );

看到这里的一些回复很有趣。 如果您不担心旧版浏览器支持 (IE6+),请跳至插值方法,因为它非常高效。

最向后兼容(回到 IE6)并且仍然非常高效的按值复制字符串的方法之一是将其拆分为一个新数组并立即将新数组作为字符串重新加入:

let str = 'abc';
let copiedStr = str.split('').join('');
console.log('copiedStr', copiedStr);

幕后花絮

上面所做的是调用 JavaScript 来拆分字符串,不使用任何字符作为分隔符,这会将每个单独的字符拆分为新创建的数组中它自己的元素。这意味着,在短时间内,copiedStr 变量看起来像这样:

['a', 'b', 'c']

然后,立即重新加入 copiedStr 变量,每个元素之间不使用任何字符作为分隔符,这意味着新创建的数组中的每个元素都被推回到一个全新的字符串中,有效地复制字符串。

执行到最后,copiedStr是自己的变量,输出到控制台:

abc

性能

平均而言,这在我的机器上大约需要 0.007 毫秒 - 0.01 毫秒,但您的情况可能会有所不同。在 4000 个字符的字符串上测试,该方法复制一个字符串的最大时间为 0.2 毫秒,平均时间约为 0.14 毫秒,因此它仍然具有可靠的性能。

谁还在乎 Legacy 支持?/Interpolation Method

但是,如果您不担心遗留浏览器的支持,那么这里的一个答案中提供的 interpolation 方法 by Pirijan,是一个非常高效且易于复制的字符串:

let str = 'abc';
let copiedStr = `${str}`;

在相同的 4,000 个字符长度的字符串上测试 interpolation 的性能,我发现平均时间为 0.004 毫秒,最大值为 0.1 毫秒,最小值达到惊人的 0.001 毫秒(非常频繁)。