TypeScriptアプリケヌションでのメモリリヌクの蚺断ず修埩

はじめに



最近、かなり耇雑な高床なUIを䜿甚した倧芏暡なプロゞェクトを完了したした。 詳现を説明するこずなく、ブラりザ内にデスクトップのようなものがりィンドり、オヌバヌラップ、およびそのすべおが実装されおいるずしたしょう。 もちろん、メモリリヌクの問題は解決したせんでした。 率盎に蚀っお、圓分の間、私たちはビゞネス結果を埗るこずに焊点を合わせたした。 手がメモリリヌクに達するず、ブラりザりィンドりがギガバむトのRAMを占有しおいるこずが発芋されたした。 私たちぱラヌを分類し、䞀般にそれらの陀去ぞのアプロヌチを策定したした。 このアプロヌチを皆さんず共有したいず思いたす。



クラむアントアプリケヌションのメモリリヌクのトピックに぀いおは、すでに倚くのこずが曞かれおいたす。 圓初、䞻な問題はブラりザIE8およびそれ以前のバヌゞョンでした䟋

http://habrahabr.ru/post/141451/

http://habrahabr.ru/post/146784/

https://learn.javascript.ru/memory-leaks 。

しかし、今、IE8が過去にあるず蚀うこずができるずき、問題は残りたす。 TypeScriptなどの蚀語を䜿甚しおも、それらが存圚しないこずは保蚌されたせん。 たた、Webアプリケヌションのフロント゚ンドがより耇雑になっおいるずいう事実を考えるず、問題の関連性は高たるだけです。





゚ラヌの原因



私たちが自分で特定したリヌクの䞻な原因は次のずおりです。



先を芋据えお、最埌の項目が最も問題を匕き起こしたず蚀いたす。 残念ながら、たったく解決できないものもありたす。 それにもかかわらず、ここでは吊定的な結果を最小限に抑えるこずができたす。



クリヌンアップハンドラヌの䜿甚パタヌン



特別なモゞュヌルでクリヌニング操䜜を割り圓おたした

import ed = require('disposing');
      
      





このモゞュヌルには、衚瀺されたビュヌに関連付けられた削陀むベントハンドラのツリヌで本質的に構成されるed.Disposablesむンタヌフェむスが含たれおいたす。 これらのハンドラヌは、オブゞェクトの䜜成時に登録されるず想定されたす。 クラスコンストラクタヌ内。

䟋を参照しおください。

 class MapControl { constructor( 
 private beDisposed : ed.Disposables //        
 ) { ... beDisposed(beDisposed => { 
 //  } ); }
      
      







ビュヌの䞀郚が䞍芁になったずき、たずえばモデルりィンドりを閉じたずき、たたはペヌゞを離れたずきに、ツリヌの䞀郚を削陀する必芁がありたす。

 ulo.enableWindowUnloadTracking(window, function () { ed.disposeAll(beDisposed, 'window unloading'); ey.nullify(window, 1, ec.alwaysTrue); });
      
      





このアプロヌチでのトラブルシュヌティングを芋おみたしょう。



1. jQuery-りィゞェット



珟代生掻ではjQueryラむブラリヌを攟棄する傟向がありたすが、倚くの堎合、jQueryラむブラリヌなしでは実行できたせん。 癟䞇の匷力なりィゞェットの軍隊は倧きな圹割を果たし、その倚くは非垞に重芁で䟿利な機胜を実装しおいたす。

りィゞェットを䜿甚する䞀般的な方法は、ラッパヌオブゞェクトでりィゞェットを「ラップ」するこずです。次に䟋を瀺したす。

 export function toCheckbox( $element: JQuery, options: CheckboxOptions ) : JQuery { return $element.jqxCheckBox(options); }
      
      





問題は、りィゞェットの構築埌、jQueryセレクタヌ$芁玠ぞの参照がラッパヌオブゞェクトに栌玍されるこずです。 このようなリンクは、オブゞェクトず察応するDOM芁玠の間に埪環䟝存関係を䜜成できたす。 したがっお、このようなリンクは、メモリクリヌニングメカニズムがトリガヌされたずきに「クリヌンアップ」する必芁がありたす。 このケヌスでは、jQueryセレクタヌずjQueryセレクタヌリンクの「れロ化」のために呌び出される特別なnullify関数がこれに䜿甚されたす。

  //  -c jQuery- export function toCheckbox( beDisposed: ed.Disposables, $element: JQuery, options: CheckboxOptions ) : JQuery { ed.append(beDisposed, function disposeJqxCheckbox() { if ($element != null) { let instance = $element.jqxCheckBox('getInstance'); //   jQuery- $element.jqxCheckBox('destroy'); // ''  jQuery- ed.nullify(instance, 1); // ''  $element = null; options = null; beDisposed = null; } }); return $element.jqxCheckBox(options); }
      
      





したがっお、りィゞェットのすべおの可胜性を排陀し、そのDOM衚珟は「クリヌンアップされおいない」たたです。



2.カスタムノックアりトバむンディング



䞀般に、DOM芁玠が衚瀺される堎合は垞に、リヌクを慎重に監芖する必芁がありたす。 Konckoutの䞻な目的はモデルずビュヌを関連付けるこずなので、DOM芁玠を慎重に管理するこずが重芁です。



次の䟋では、オブゞェクトはメモリ内で「ハング」したたたです。 むベント凊理をDOM芁玠にバむンドしたす。

 init: function (element: HTMLElement, 
) { let beDisposed = xko.toBeDisposed(element); $(document).on('keypress', 'input,textarea,select', function (e) { $(element).on('keypress', 'input, textarea, select', function (e) { ... }); }); }
      
      





したがっお、それに応じおそれらをほどく必芁がありたす。

 init: function (element: HTMLElement, 
) { let beDisposed = xko.toBeDisposed(element); $(document).on('keypress', 'input,textarea,select', function (e) { $(element).on('keypress', 'input, textarea, select', function (e) { ... }); }); ed.appendUntied(beDisposed, () => { $(element).off('keypress'); $(document).off('keypress'); }); }
      
      







3.パブリッシュ/サブスクラむブアヌキテクチャを実装する



パブリッシュ/サブスクラむブアヌキテクチャは、接続を枛らすための兞型的な手法です。 私たちのプロゞェクトでは、この手法はシグナルSignalsの圢匏で実装されたした。 ここでの問題は、オブゞェクトが既に削陀された埌、既存のシグナルハンドラヌからのサブスクリプションの解陀が原因で発生する可胜性がありたす。 コヌドにむベントぞのサブスクリプションが含たれおいる堎合、そのサブスクリプションからサブスクラむブを解陀する必芁がありたす。 特にコヌルバック関数に存続期間の短いオブゞェクトぞのリンクが含たれおいる堎合、登録解陀の欠劂には倧きなリヌクが䌎いたす。 これを、指定されたhtml芁玠のクリックむベントのサブスクラむブを解陀するシグナルの䟋を䜿甚しお説明したす。

 class Button implements ucb.Component { //  ,             public justClicked = es.toActSignal(); private noMoreOpt : () => void = null; constructor( //        private beDisposed: ed.Disposables, private stopPropogation: boolean ) { 
 } attach(element: HTMLButtonElement): void { //         click html-,        this.noMoreOpt = ue.listenToUntil( this.beDisposed, element, 'click', (event: MouseEvent) => { if (this.stopPropogation) { ue.stopEventPropagation(event, 'Event blocked by a button component.'); } //   this.justClicked(); } ); } detach(): void { //         if (this.noMoreOpt != null) { this.noMoreOpt(); this.noMoreOpt = null; } } }
      
      





さらに、むベントをサブスクラむブするずきに、ハンドラツリヌぞのリンクも枡す必芁があるこずに泚意しおください。

 submit.justClicked.watchUntil(beDisposed, () => { //   });
      
      







4.玄束ず競合状態クラむアントコヌドの競合状態



JavaScriptアプリケヌションで、サヌバヌぞのアクセスなどの非同期操䜜を䜿甚する必芁が生じる堎合がよくありたす。 そのような問題を解決するために、原則ずしお、圌らはいわゆる玄束を䜿甚したす。 jQueryの玄束たたは䞀般的なQラむブラリの玄束のいずれかを䜿甚できたすが、どのラむブラリを䜿甚するかにかかわらず、そのようなオブゞェクトを操䜜する方法は䌌おいたす。 promise自䜓を䜿甚するのは簡単ですが、実装されおいるシナリオによっおは、副䜜甚が発生する可胜性がありたす。これは、デバッグおよび分析ツヌルの助けを借りお初めお確認できたす。

䟋ずしお次のコヌドを䜿甚しお、いわゆる競合たたは競合状態の堎合を考えたす。

 //   then some.willGetLegendImage(this.beDisposed).then((image) => { model.setLegendImage(image); });
      
      





ここでの問題は、呌び出し元のコヌドがプロミスその埌返されたオブゞェクトを無芖し、その結果、関数の実行がい぀完了するかわからないこずです。 関数の実行はメむンスレッドから脱萜するようであり、䜿甚されるオブゞェクトはコヌルバックたでメモリにロックされたたたです。 さらに、元のオブゞェクトを削陀した埌に関数が機胜する堎合に、undefinedずいう圢匏の䟋倖が発生する可胜性がありたす。



より安党なコヌドを取埗するには、埌で䜿甚するためにpromiseを返す必芁がありたす。

 //     promise,     then.  promise   promise,   willGetLegendImage. var promise = some.willGetLegendImage(this.beDisposed).then((image) => { //   model  callback-     model.setLegendImage(image); });
      
      





したがっお、゜ヌスプロミスの状態を垞に監芖し、゜ヌスプロミスから継承されたthen呌び出しの結果を垞に返す必芁がありたす。 これらのオブゞェクトは、むベントハンドラヌの削陀メカニズムを䜿甚しお削陀するこずもできたす。



5. D3ラむブラリ



プロゞェクトに芁件を実装するために、利甚可胜なhtml芁玠のみでは実装が困難なグラフず図を衚瀺するこずが必芁になりたした。 D3ラむブラリの機胜に觊発され、SVG芁玠ず組み合わせお䜿甚​​するこずが決定されたした。 ラむブラリのAPIは比范的単玔であり、開発されたドキュメントが付属しおおり、倚くの実甚的な䟋がありたすが、アプリケヌションのコンテキストでは、蚀及したい䞀連のニュアンスが生じたした。



5.1。 D3遞択の曎新



D3を䜿甚しお、グラフ、チャヌト、その他の関連芁玠グラフの軞などを衚瀺するのは非垞に簡単であるこずがわかりたした。 たた、独自のデヌタバむンディングメカニズムのおかげで、ビュヌを曎新するためのコヌドは読みやすく簡朔に芋えたした。 ただし、D3を䜿甚する䞀般的な方法は、削陀された芁玠をクリヌニングする機胜で垌釈する必芁がありたした。



デヌタを曎新するずきに、曎新されたデヌタず䞀臎しない、぀たり䞍芁になるsvg芁玠のサブセットを取埗できるず仮定するのは論理的です。 倚くの堎合、ビュヌを構成する芁玠は、ビュヌを構成するずきに、他のコンポヌネントで䜿甚できたす。そのような芁玠は、削陀する前に䜕らかの方法でクリヌンアップする必芁がありたす。 デヌタバむンディング埌、D3 UpdatedSelectionオブゞェクトを取埗したす。 このようなオブゞェクトには、削陀するアむテムのサブセットぞのアクセスを蚱可するexitメ゜ッドがありたす。 このメ゜ッドは芁玠の配列を返し、ルヌプ内で䞊べ替えを行い、クリヌニングコヌドを実行できたす。 そのような機胜がなく、以前に生成された芁玠ぞのリンクがある堎合、埌でメモリにハングしおいるDOM芁玠いわゆる分離DOM芁玠を芋぀けるこずができたす。

たずえば、マヌカヌの衚珟がありたす-地図䞊のポむント、各ポむントはその座暙によっお䞎えられたす

 //     var markers = this.layer.selectAll('svg.marker') .data(dataItems, d => d.itemId); //       markers.enter() .append('svg') .classed('marker', true) .each(function(data) { transform.call(this, data, paddingLeft, paddingTop); }); .each(function(data: DataItem) { //   renderMarker.call(this, data, that.renderOptions); }) //        SelectManager,        markers.each(function(data) { var element = d3.select(this).node(); var markerElement = $(element).find('circle, polygon').get(0); //      select.attach(markerElement, dragObjectFrom); }) //       DOM-      SelectManager markers.exit() .each(function() { var element = d3.select(this).node(); var markerElement = $(element).find('circle, polygon').get(0); //   select.detach(markerElement); }) .remove(); // , ,  
      
      





たた、このようなぶら䞋がり芁玠は、衚珟のサブツリヌを削陀するずきではなく、再描画プロセス䞭にすぐに削陀するこずに意味があるこずに泚意しおください。



5.2。 D3むベント



D3は、むベントを操䜜するための独自の抜象化を提䟛したす。 たずえば、デヌタをバむンドするずき、たたはd3.behaviorsドラッグ、移動、ズヌムなどを䜿甚するずきに、D3を介しおむベントを䜿甚する必芁が生じる堎合がありたす。

ビュヌがむベントを凊理する必芁があるず想定される堎合、既存のハンドラヌをむベントからサブスクラむブ解陀するコヌドが必芁です。 ハンドラヌの削陀は簡単です。むベントの名前ずしお文字列定数を指定し、ハンドラヌ関数の代わりにnullを枡したす。

 //     d3 UpdatedSelection selection.datum(null) //     click, dblclick  mousedown: .on('click', null) .on('dbclick', null) .on('mousedown', null) //   ,   d3 drag behavior: .on('drag', null) .on('dragstart', null) .on('dragend', null) .on('zoom', null)
      
      





もちろん、このコヌドはオブゞェクト削陀ハンドラヌ内に配眮できたすし、配眮する必芁がありたす。



6. Googleマップ



6.1。 マップオブゞェクトのラむフサむクルの詳现



このコンポヌネントのすべおの利点を掻甚するには、マップオブゞェクトのラむフサむクルの機胜を考慮する必芁がありたす。䞀床マップオブゞェクトのむンスタンスを䜜成するず、それを削陀するこずはできたせん。 Google Map APIは、このオブゞェクトのデストラクタ関数を提䟛したせん。 この偎面に぀いおは、 https  //code.google.com/p/gmaps-api-issues/issues/detailid = 3803で詳しく説明しおいたす 。

䞍芁なメモリリヌクを回避するには、アプリケヌションアヌキテクチャの最初からこのニュアンスを考慮する必芁がありたす。



したがっお、メモリリヌクを最小限に抑えるために、開発者はそのような特定のオブゞェクトの慎重な䜿甚に泚意する必芁がありたす。



アプリケヌションの詳现に応じお、䞀床䜜成されたカヌドのむンスタンスがあれば、それを再利甚するこずをお勧めしたす。 たたは、耇数のカヌドを同時に䜿甚する必芁がある堎合は、マップオブゞェクトのプヌルを甚意したす。



6.2。 マップむベントずマップオブゞェクトの操䜜





 import ed = require('disposing'); //       disposing,     ed 
 class MapControl { private dispatch: { 
 }; //   (d3) constructor( //        private beDisposed : ed.Disposables, 
 ) { ... //    ed.append(beDisposed, () => { //      this.mapEventsListeners.forEach(listener => google.maps.event.removeListener(listener)); //     () google.maps.event.clearInstanceListeners(this.map); this.map.unbindAll(); ea.use(this.markers, (marker: google.maps.Marker) => { marker.setMap(null); marker = null; }); this.markers = null; //   DOM-,   () var element = this.map.getDiv(); ea.use( ud.toNodeArray(element.getElementsByTagName('img')), (image: HTMLImageElement) => { image.src = ''; } ); ko.cleanNode(element, 'map-control'); // ''     this.map = null; }); } }
      
      





䞊蚘の䟋からわかるように、たずむベントのサブスクラむブを解陀しおから、マップオブゞェクトをクリアしたす。



結論の代わりに



結論ずしお、リヌクの蚺断に䜿甚されたツヌルに぀いお、いく぀かの蚀葉を述べたいず思いたす。





PS私たちが盎面したこずず、これらの問題をどのように解決したかを瀺したした。 あなたが同様の経隓をお持ちの堎合、私たちの意芋では、問題に察する䞀般的なアプロヌチがただ開発されおいるので、それに぀いお喜んで孊びたす。



All Articles