web-dev-qa-db-ja.com

JavaScriptに文字列をディープコピーさせる方法は?

次のようなJavaScriptコードがあります。

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

その後、関数はいくつかの大きな文字列(100 MB以上)で呼び出されます。各文字列で見つかった短いIDのみを保存します。ただし、Google Chromeのサブストリング関数(私のコードでは実際には正規表現)は、オリジナルを参照する「スライスされたストリング」オブジェクトのみを返します。したがって、myFuncへの一連の呼び出しの後、一時的なhuge_stringオブジェクトをガベージコレクションできないため、chromeタブのメモリが不足します。

文字列idのコピーを作成して、huge_stringへの参照を維持せず、huge_stringをガベージコレクションできるようにするにはどうすればよいですか。

enter image description here

46
AffluentOwl

JavaScriptのECMAScriptの実装はブラウザごとに異なりますが、Chromeでは、多くの文字列操作(substr、slice、regexなど)は、文字列のコピーを作成するのではなく、元の文字列への参照を保持します。これはChromeの既知の問題です( Bug#2869 )。文字列のコピーを強制するには、次のコードが機能します。

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

このコードは、文字列の前にスペースを追加することで機能します。この連結により、Chromeの実装で文字列がコピーされます。次に、スペースの後のサブストリングを参照できます。

ソリューションに関するこの問題は、ここで再現されました: 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);

enter image description here

43
AffluentOwl

文字列、オブジェクト、配列などにObject.assign()メソッドを使用します。

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

Object.assignは、オブジェクト内のキーとそのプロパティ値のみをコピーすることに注意してください(1レベルのみ)。ネストされたオブジェクトのディープクローンについては、次の例を参照してください。

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
9
Daniel C. Deng

アレイにプッシュするときに問題が発生していました。 .next()関数を使用して結果を反復処理するときに変更されたオブジェクトの値を参照していたため、すべてのエントリは同じ文字列になります。文字列をコピーして、配列の結果に一意の値を取得できるようにしたのは次のとおりです。

while (results.next()) {
  var locationName = String(results.name);
  myArray.Push(locationName);
}
3
Kyle s

次を使用できます。

 String.prototype.repeat(1) 

うまくいくようです。 repeatのMDNドキュメント を参照してください。

0
nuttybrewer