{"id":22285,"date":"2015-07-17T13:54:12","date_gmt":"2015-07-17T13:54:12","guid":{"rendered":"https:\/\/blog.jetbrains.com\/webstorm\/?p=7654"},"modified":"2021-06-11T12:15:46","modified_gmt":"2021-06-11T11:15:46","slug":"node-js-profiling-in-webstorm-part-2-memory-profiling","status":"publish","type":"webstorm","link":"https:\/\/blog.jetbrains.com\/zh-hans\/webstorm\/2015\/07\/node-js-profiling-in-webstorm-part-2-memory-profiling","title":{"rendered":"Node.js Profiling in WebStorm. Part 2: Memory Profiling."},"content":{"rendered":"<p>We continue talking about our new Node.js profiling features in WebStorm 10.&nbsp;In our <a href=\"https:\/\/blog.jetbrains.com\/zh-hans\/webstorm\/2015\/05\/node-js-profiling-in-webstorm-part-1-cpu-profiling\">previous post<\/a> we explored CPU profiling. In this one we&#8217;ll dive into the strategies for memory problems investigation and see how WebStorm can help us apply them.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-heap_profiling-cover.png\" alt=\"heap_profiling-cover\" width=\"640px\"><\/p>\n<p><strong>Table of content<\/strong><\/p>\n<ul>\n<li><a href=\"#background\">Background information<\/a><\/li>\n<li><a href=\"#takingHeapSnapshots\">Taking heap snapshots in WebStorm<\/a><\/li>\n<li><a href=\"#findingLeaks\">Finding memory leaks in heap snapshots<\/a>\n<ul>\n<li><a href=\"#containmentView\">Containment view<\/a><\/li>\n<li><a href=\"#search\">Search and Mark<\/a><\/li>\n<li><a href=\"#navigateInMainTree\">Navigate in Main Tree and Jump to Source<\/a><\/li>\n<li><a href=\"#biggestObjectsView\">Biggest objects view<\/a><\/li>\n<li><a href=\"#summaryView\">Summary view<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#comparingSnapshots\">Comparing Heap snapshots<\/a><\/li>\n<li><a href=\"#webSnapshots\">Heap snapshots of web applications<\/a><\/li>\n<li><a href=\"#snapshotsRemote\">Taking heap snapshots in remote environments<\/a><\/li>\n<li><a href=\"#conclusions\">Conclusions<\/a><\/li>\n<\/ul>\n<h2 id=\"background\">Background information<\/h2>\n<p>JavaScript is a language with automatic memory management. That means that the JavaScript engine itself takes care of allocating space for your objects and collecting objects when they are not needed.<\/p>\n<p>Node.js, io.js, and Google Chrome use the same open-source JavaScript engine, called <a href=\"https:\/\/code.google.com\/p\/v8\/\" target=\"_blank\" rel=\"noopener\">V8<\/a>.<\/p>\n<p>In this article we will talk about the <em>V8 JavaScript heap<\/em>, i.e. only about the memory area that is managed by the JavaScript engine and contains JavaScript objects.<\/p>\n<p><em>So why should we then care about memory profiling?<\/em> Although memory is allocated, freed, and defragmented automatically, it is still our application code that requests object creation, and the application should indicate when objects are not needed. The application is responsible for the object lifecycle. That is why it&#8217;s important to use an optimal memory consumption strategy and, of course, watch out for errors.<\/p>\n<p>Memory problems are especially dangerous for Node.js applications. Node.js servers tend to run for a long time, so any memory leaks would accumulate over time.<\/p>\n<p>The three main concepts around application memory management are memory budget, memory leaks, and the speed of memory consumption. Let\u2019s talk about them.<\/p>\n<p><strong>Memory budget<\/strong><\/p>\n<p>Our application operates a limited amount of memory, and big data structures such as caches or queues may eventually consume a significant part of memory. It may result in running out of memory or frequent garbage collection invocations. Taking a heap snapshot in this case will help us find out which of the queues or caches run \u201cout of the budget.\u201d<\/p>\n<p><strong>Memory leaks<\/strong><\/p>\n<p><em>How does Garbage Collector (GC) determine that an object is not needed?<\/em> There is a special group of objects, called <em>GC roots<\/em> that are known to exist. Hence, they are used as a starting point to determine which objects can be collected.<\/p>\n<p>GC roots include objects in the current call stack (i.e. referenced from the code that is executed right now), objects that belong to the JavaScript engine, such as compilation cache, and objects backed by native (non-JavaScript) objects. Garbage Collector iterates over the graph of objects (heap) starting from the GC roots, following the references between objects. Any object that cannot be reached from any GC root is garbage and subject to collection.<\/p>\n<p><strong>Memory leaks<\/strong> occur when objects that are not needed for the application anymore, but are still referenced from one of GC roots, tend to accumulate over time. Leaks may be small, but still dangerous due to their tendency to grow. The causes of memory leaks may include errors in application cache management, inaccurate use of closures, or keeping detached DOM nodes in JavaScript variables.<\/p>\n<p><strong>Memory consumption problems<\/strong> may arise if you allocate objects too frequently, forcing too-frequent Garbage Collector cycles, which in turn leads to performance degradation.<\/p>\n<p><strong>Relevant articles<\/strong><\/p>\n<p>We highly recommend that you read the <a href=\"https:\/\/developer.chrome.com\/devtools\/docs\/javascript-memory-profiling\" target=\"_blank\" rel=\"noopener\">JavaScript Memory Profiling<\/a> guide on the Chrome website. There is also another great article by Addi Osmany that we highly recommend: <a href=\"http:\/\/addyosmani.com\/blog\/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools\/\" target=\"_blank\" rel=\"noopener\">Taming The Unicorn: Easing JavaScript Memory Profiling In Chrome DevTools<\/a>.<\/p>\n<p>We believe that we can also contribute to the task of memory analysis and make it easier for the developers.<\/p>\n<p>WebStorm offers you the following features that can help hunt down memory leaks:<\/p>\n<ul>\n<li>Taking V8 heap snapshots in the runtime,<\/li>\n<li>Heap snapshots view,<\/li>\n<li>Heap snapshots difference view (in WebStorm 10.0.3+).<\/li>\n<\/ul>\n<h2 id=\"takingHeapSnapshots\">Taking heap snapshots in WebStorm<\/h2>\n<p>To take heap snapshots while running a Node.js app in WebStorm, select<em> Allow taking heap snapshots<\/em> in Node.js Run Configuration in V8 Profiling tab:<\/p>\n<p><img class=\"alignnone size-full \/><\/p>\n<p>You&#8217;ll need to install the <a href=\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-node-run-config.png\" alt=\"node-run-config\" width=\"640\"><\/p>\n<p>You will need to install the <a href=\"https:\/\/github.com\/node-inspector\/v8-profiler\" target=\"_blank\" rel=\"noopener\">v8-profiler<\/a> node module, which contains the JavaScript binding for the V8 <em>takeSnapshot()<\/em> method. WebStorm generates a special proxy code, which runs the in-process server. When it&#8217;s time to take a heap snapshot, WebStorm sends this server a notification through a <em>Communication port<\/em> specified in the Run configuration.<\/p>\n<p>So when the application is up and running, you can use a new <em>Take Heap Snapshot<\/em> action in the Run\/Debug tool windows. You&#8217;ll be asked to select a file where the snapshot will be saved. You can choose to open it immediately:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-save_snapshot.png\" alt=\"save_snapshot\" width=\"399\" height=\"435\"><\/p>\n<p>The <em>Show hidden data<\/em> check-box allows you to see internal V8 objects and hidden links.<\/p>\n<p><em>Note:<\/em> You can also take heap snapshots in Google Chrome and then open them in WebStorm.<\/p>\n<h2 id=\"findingLeaks\">Finding memory leaks in heap snapshots<\/h2>\n<p>In this article we&#8217;ll be using the <a href=\"http:\/\/info.meteor.com\/blog\/an-interesting-kind-of-javascript-memory-leak\" target=\"_blank\" rel=\"noopener\">Interesting kind of JavaScript memory leak<\/a> example from Meteor\u2019s blog. It&#8217;s a great case with a perfect explanation that illustrates the potential danger of closures and why we might need memory profiling.<\/p>\n<p>Here is the code:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-title=\"\">var replaceThing = function () {\n  var originalThing = theThing;\n  var unused = function () {\n    if (originalThing)\n      console.log(&quot;hi&quot;);\n    };\n    theThing = {\n      longStr: new Array(1000000).join(&#039;*&#039;),\n      someMethod: function () {\n      console.log(someMessage);\n    }\n  };\n};\nsetInterval(replaceThing, 1000);<\/pre>\n<p>In this example the value of <code>originalThing<\/code> will be kept in the lexical scope of <code>theThing.someMethod<\/code> closure. Since <code>originalThing<\/code> gets reassigned again and again, we are going to have a chain looking like:<\/p>\n<p><code>theThing -&gt; someMethod -&gt; (lexical scope) -&gt; originalThing -&gt; someMethod -&gt; (lexical scope) -&gt; etc.<\/code><\/p>\n<p>Please refer to the original <a href=\"http:\/\/info.meteor.com\/blog\/an-interesting-kind-of-javascript-memory-leak\" target=\"_blank\" rel=\"noopener\">article<\/a> for the detailed explanation.<\/p>\n<p>We are going to check if we can observe the predicted situation in the heap snapshot.&nbsp;So let\u2019s run the example:<\/p>\n<ol>\n<li>Create Node.js Run Configuration and select <em>Allow taking heap snapshots<\/em>.<\/li>\n<li>Start Run Configuration and after a few seconds record the first snapshot.<\/li>\n<li>Wait for about 30 seconds and record the second snapshot.<\/li>\n<\/ol>\n<p>Now let\u2019s open the first snapshot. We\u2019ll see the <em>Containment view<\/em>:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-containment_view.png\" alt=\"containment_view\" width=\"640\" height=\"434\"><\/p>\n<h3 id=\"containmentView\">Containment view<\/h3>\n<p>The <em>Containment view<\/em> shows the relationships between objects. In the main tree GC roots occupy the first level and you can unfold objects to see their children.<\/p>\n<p>If you select any object in the main tree, its details are shown below: the path to the object from one of the GC roots and all other nodes that reference this object. Usually a heap dump is a dense graph, where each object has many links to and from it. There are many cyclic references and references from system objects.<\/p>\n<p>In general, the Containment view is essential for understanding why an object is not collected, i.e. who holds a reference to it.<\/p>\n<p>However, we see a really huge tree with many links and internal objects so&#8230; what will our first steps be?<\/p>\n<h3 id=\"search\">Search<\/h3>\n<p>First of all, we can use the Search feature. WebStorm searches for objects by:<\/p>\n<ul>\n<li>variable, parameter, or function name (\u201cLink names\u201d),<\/li>\n<li>class names,<\/li>\n<li>text string contents,<\/li>\n<li>snapshot object IDs (every object inside a heap snapshot has its unique ID),<\/li>\n<li>marks given by the user (we\u2019ll talk about those later).<\/li>\n<\/ul>\n<p>Since we expect a memory leak to occur around the inner closure and <code>originalThing<\/code> variable, let\u2019s search for it:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-search_action.png\" alt=\"search_action\" width=\"523\" height=\"461\"><\/p>\n<p>On this screenshot you can also see the &#8220;artificial&#8221; groups of GC roots that are created by V8.<\/p>\n<p>The search results are shown in the bottom view:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-search_results_and_mark_action.png\" alt=\"search_results_and_mark_action\" width=\"578\" height=\"509\"><\/p>\n<p>We can see that there are multiple instances of the <code>originalThing<\/code> variable, each kept in a corresponding context object. So our prediction about the memory leak has come true.<\/p>\n<h3>Mark action<\/h3>\n<p>To easily differentiate same-named objects and to be able to return to them again, we can use the <em>Mark<\/em> action. The object is then associated with a text mark and can later be found via search.<\/p>\n<h3 id=\"navigateInMainTree\">Navigate in Main Tree and Jump to Source actions<\/h3>\n<p>You can easily navigate from the search results (and from other views) to the same object in the main tree (i.e. the main tree in the Containment view), or jump to the source code, by using context actions:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-jump_to_source.png\" alt=\"jump_to_source\" width=\"369\" height=\"83\"><\/p>\n<p>Just to complete the picture, let&#8217;s see the same leaking chain starting from <code>theThing<\/code> object:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-origin_of_chains.png\" alt=\"origin_of_chains\" width=\"640\" height=\"310\"><\/p>\n<p>There are other ways to explore the snapshot.<\/p>\n<p>Very often you have no assumptions what could be causing the leak or whether there&#8217;s a leak at all. One way to hunt for leaks is working with the <em>Biggest objects<\/em> view.<\/p>\n<h3 id=\"biggestObjectsView\">Biggest objects view<\/h3>\n<p>The <em>Biggest objects<\/em> view sorts objects by their retained size and shows the top list. Looking at the biggest objects might help in situations when there is a single root of memory accumulation, such as incorrectly written cache or an overflowed queue.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-7682\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-biggest_obj.png\" alt=\"biggest_obj\" width=\"628\" height=\"409\"><\/p>\n<p>In our case we observe many <code>longString<\/code> string objects.<\/p>\n<p>In the Biggest objects view we do not see any specific links that identify the object, but there is a Details view in the bottom that shows the path to the selected object from one of the GC roots and retainers (objects that also hold the references to the current object).<br \/>\nYou can navigate to the same object in the Containment view using the <em>Navigate in Main Tree<\/em> action.<\/p>\n<p>Another way of investigating heap snapshots is to use the Summary view.<\/p>\n<h3 id=\"summaryView\">Summary view<\/h3>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-summary_view.png\" alt=\"summary_view\" width=\"619\" height=\"393\"><\/p>\n<p>In the <em>Summary view<\/em> all the objects are grouped by their type. If one type dominates the view, it might point to the memory leak. You can navigate to the parent object where the objects of this type are hold (using the <em>Navigate in Main Tree<\/em> action or just by looking into the Details view) and check if it is the leak root.<\/p>\n<p>Another good hint is to look at the <em>Distance<\/em> parameter for the objects of the same type (distance is the number of steps between the current object and the GC root on the main path). If objects of the same type have different distances, it means they were created by different parts of the code or with a different recursion level. This may be a clue, if you don\u2019t expect different creation paths for objects of this type.<\/p>\n<p>In our case, we see that string objects take the most space. The <em>Different distances<\/em> rule also works well in our case\u2014we see our heavy strings are all created at different depths.<\/p>\n<p><a href=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-summary_view_dist.png\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-7683\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-summary_view_dist.png\" alt=\"summary_view_dist\" width=\"640\" height=\"337\"><\/a><\/p>\n<h2 id=\"comparingSnapshots\">Comparing Heap snapshots<\/h2>\n<p>Starting with WebStorm 10.0.3, it is possible to see the difference between heap snapshots in a special view.<\/p>\n<p>This feature allows you to get a clearer picture: you can concentrate more on the objects that keep reappearing or got heavier, and less on the \u201cbackground\u201d stuff.<\/p>\n<p>In an ideal situation and after a long running time, Heap Diff would directly point to the memory leaks. However, you should always expect some dynamic changes between snapshots: event objects will be created, compilation and recompilation would occur in runtime, and so on.<\/p>\n<p>Another idea behind Heap Diff is that it assists in analyzing slow-running memory leaks.<\/p>\n<p>Suppose you have a leak that will only show up after hours of work. If you just analyze a single snapshot, other \u201chealthy\u201d objects might be bigger than the leak that may not have accumulated enough, making it difficult to detect the leak with the usual strategies (such as biggest objects or class groups).<\/p>\n<p>With Heap Diff, you are likely to notice a memory growth sooner, since other heavy objects are likely to be filtered out.<\/p>\n<p>So let\u2019s examine the difference between the two snapshots that we took in our experiment.<\/p>\n<p><strong>Opening the Diff view<\/strong><\/p>\n<p>Use the <em>Compare with<\/em> action, select the second snapshot in the dialog, and specify whether it is a subsequent or previous snapshot. A Heap snapshot view for the second snapshot will also be opened in the other tab.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-open_dff.png\" alt=\"open_dff\" width=\"564\" height=\"405\"><\/p>\n<p><strong>Summary Diff view<\/strong><\/p>\n<p>In the Summary Diff we can see aggregated information about each type of objects:<\/p>\n<ul>\n<li>The difference in the number of objects of each type (Count Diff),<\/li>\n<li>The number of created and destroyed objects (Count),<\/li>\n<li>The difference in the sum of self-sizes of objects (Size Diff),<\/li>\n<li>The sum of self-sizes in the \u201cbefore\u201d and \u201cafter\u201d snapshots (Size).<\/li>\n<\/ul>\n<p>If you expand a type node, you will see the objects of that type that were created\/destroyed with their retained sizes. (Only the first 50 objects are shown.)<\/p>\n<p>This view helps locate the types which led to the biggest memory growth, or the types with overly volatile behavior, i.e. with objects that were created\/destroyed too often.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-summary_diff_view.png\" alt=\"summary_diff_view\" width=\"640\" height=\"353\"><\/p>\n<p><strong>Biggest Objects Diff<\/strong><\/p>\n<p>In the Biggest Objects Diff view, we show how the size of the objects changed and which objects were added\/removed. Growing leaks that are big enough are likely to be highlighted in this view.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-biggest_obj_diff.png\" alt=\"biggest_obj_diff\" width=\"635\" height=\"177\"><\/p>\n<p>Both in the Summary Diff and the Biggest Objects Diff views, you can invoke context actions to mark objects (the mark will be propagated to the originating snapshots), or to navigate to the same object in the originating snapshot (provided that this snapshot is open).<\/p>\n<p>In our case we observe that the number of strings has increased by 75% (in the Summary view) and we see the same-size big string objects added or not changed (in the Biggest Objects view).<\/p>\n<h2 id=\"webSnapshots\">Heap snapshots of web applications<\/h2>\n<p>Web apps also leak. Since Google Chrome and Node.js have the same JavaScript engine, you can take a snapshot in Chrome for your web app and open it using WebStorm: <strong>Main menu | Tools | V8 Profiling | Analyze V8 heap Snapshot<\/strong>.<\/p>\n<p>Of course, Chrome has its own support for working with heap snapshots. Give WebStorm a try and tell us if our new features for searching by object names, navigation inside a snapshot, and navigation to code were useful and where we are missing something!<\/p>\n<p>Web apps snapshots have a more complicated structure. There is one more player on the scene: the DOM tree. So there are typical leaks related to patterns of working with DOM objects in JavaScript.<\/p>\n<p>The most well-known leak is keeping a reference to a DOM object detached from the document in the JavaScript code. You can find such objects under the Detached DOM trees synthetic GC root, <a href=\"https:\/\/plus.google.com\/u\/0\/+AddyOsmani\/posts\/hEMupRLRJSF\" target=\"_blank\" rel=\"noopener\">marked with yellow or red<\/a>.<\/p>\n<p>Here&#8217;s a an example of a heap snapshot taken for the website (here it&#8217;s <a href=\"http:\/\/www.bbc.com\/\" target=\"_blank\" rel=\"noopener\">bbc.com<\/a>) opened in WebStorm:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2015\/07\/webstorm-bbc.png\" alt=\"bbc\" width=\"640\" height=\"576\"><\/p>\n<h2 id=\"snapshotsRemote\">Taking heap snapshots in remote environments<\/h2>\n<p>In case you want to hunt for memory leaks somewhere other than the local environment, you can use a programmatic way of taking snapshots. Follow the instructions in <a href=\"https:\/\/github.com\/node-inspector\/v8-profiler\" target=\"_blank\" rel=\"noopener\">v8-profiler module description<\/a> and then open the taken snapshots using <strong>Main menu | Tools | V8 Profiling | Analyze V8 heap Snapshot<\/strong>.<\/p>\n<p><strong>Caching snapshot data<\/strong><\/p>\n<p>Analyzing a heap snapshot takes WebStorm a significant time. However, once a snapshot is analyzed and opened, you can return to it quickly: all indexes are kept in the IDE system directory.<\/p>\n<h2 id=\"conclusions\">Conclusions<\/h2>\n<p>The example that we\u2019ve investigated throughout this article perfectly illustrates that even if a piece of code hardly raises any suspicions at first sight, it can cause big problems with memory due to internal references. The bottom line is that heap profiling is essential for JavaScript applications.<\/p>\n<p>Memory monitoring and profiling is not a straightforward task. We on the WebStorm team believe that some of the features we provide in WebStorm will help you avoid memory leaks in your project and will save you time. We will continue to improve the heap introspection features.<\/p>\n<p>Thank you for reading.&nbsp;Your <a href=\"https:\/\/youtrack.jetbrains.com\/issues\/WEB#newissue\" target=\"_blank\" rel=\"noopener\">feedback<\/a> is welcome!<\/p>\n","protected":false},"author":153,"featured_media":0,"comment_status":"open","ping_status":"open","template":"","categories":[601],"tags":[],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/webstorm\/22285"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/webstorm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/types\/webstorm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/users\/153"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/comments?post=22285"}],"version-history":[{"count":2,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/webstorm\/22285\/revisions"}],"predecessor-version":[{"id":153708,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/webstorm\/22285\/revisions\/153708"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media?parent=22285"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/categories?post=22285"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/tags?post=22285"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/cross-post-tag?post=22285"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}