Home  >  Article  >  Web Front-end  >  JavaScript also talks about memory optimization_javascript skills

JavaScript also talks about memory optimization_javascript skills

WBOY
WBOYOriginal
2016-05-16 16:45:591012browse

Compared to C/C, the memory processing of JavaScript we use has allowed us to pay more attention to the writing of business logic during development. However, with the continuous complexity of business and the development of single-page applications, mobile HTML5 applications, Node.js programs, etc., phenomena such as lagging and memory overflow caused by memory problems in JavaScript are no longer unfamiliar.

This article will discuss memory usage and optimization from the language level of JavaScript. From the aspects that everyone is familiar with or have heard a little about, to the areas that most people don’t notice, we will analyze them one by one.

1. Language-level memory management

1.1 Scope

Scope is a very important operating mechanism in JavaScript programming. In synchronous JavaScript programming, it does not fully attract the attention of beginners, but in asynchronous programming, good scope control skills become the key to JavaScript development. Required skills for operators. Additionally, scope plays a crucial role in JavaScript memory management.

In JavaScript, the scopes can be formed by function calls, with statements and global scope.

Take the following code as an example:

Copy the code The code is as follows:

var foo = function() {
var local = {};
};
foo();
console.log(local); //=> undefined

var bar = function() {
local = {};
};
bar();
console.log(local); //=> {}

Here we define the foo() function and the bar() function. Their intention is to define a variable named local. But the end result was completely different.

In the foo() function, we use the var statement to declare and define a local variable. Since a scope will be formed inside the function body, this variable is defined in the scope. Moreover, the body of the foo() function does not do any scope extension processing, so after the function is executed, the local variable is also destroyed. The variable cannot be accessed in the outer scope.

In the bar() function, the local variable is not declared using the var statement. Instead, local is defined directly as a global variable. Therefore, the outer scope can access this variable.

Copy code The code is as follows:

local = {};
// here The definition is equivalent to
global.local = {};


1.2 Scope Chain

In JavaScript programming, you will definitely When encountering a scenario with multiple levels of function nesting, this is the representation of a typical scope chain.

As shown in the following code:

Copy the code The code is as follows:

function foo( ) {
var val = 'hello';

function bar() {
function baz() {
global.val = 'world;'
}
baz ();
console.log(val); //=> hello
}
bar();
}
foo();

Based on the previous description of scope, you may think that the result displayed by the code here is world, but the actual result is hello. Many beginners will start to get confused here, so let's take a look at how this code works.

Because in JavaScript, the search for variable identifiers starts from the current scope and searches outward until the global scope. Therefore, access to variables in JavaScript code can only be done outward, not the other way around.

JavaScript also talks about memory optimization_javascript skills

The execution of the baz() function defines a global variable val in the global scope. In the bar() function, when accessing the identifier val, the search principle is from inside to outside: if it is not found in the scope of the bar function, it goes to the upper level, that is, the scope of the foo() function. Search in scope.

However, the key to making everyone confused is here: this identifier access finds a matching variable in the scope of the foo() function, and will not continue to search outwards, so in the baz() function The defined global variable val has no impact on this variable access.

1.3 Closure

We know that identifier lookup in JavaScript follows the inside-out principle. However, with the complexity of business logic, a single delivery sequence is far from meeting the increasing new needs.

Let’s take a look at the following code first:

Copy the code The code is as follows:

function foo() {
var local = 'Hello';
return function() {
return local;
};
}
var bar = foo();
console.log(bar()); //=> Hello

The technology shown here to allow the outer scope to access the inner scope is closure (Closure). Thanks to the application of higher-order functions, the scope of the foo() function has been "extended".

The foo() function returns an anonymous function, which exists in the scope of the foo() function, so you can access the local variable in the scope of the foo() function and save its reference. Since this function directly returns the local variable, the bar() function can be directly executed in the outer scope to obtain the local variable.

Closure is an advanced feature of JavaScript. We can use it to achieve more complex effects to meet different needs. However, it should be noted that because the function with internal variable references is taken out of the function, the variables in the scope will not necessarily be destroyed after the function is executed until all references to the internal variables are released. Therefore, the application of closures can easily cause memory to be unable to be released.

2. JavaScript memory recycling mechanism

Here I will take the V8 engine launched by Google used by Chrome and Node.js as an example to briefly introduce the memory recycling mechanism of JavaScript. For more detailed content, you can purchase my good friend Pu Ling’s book "Speaking in Simple and Deep Language" Node.js" to learn, in which the chapter "Memory Control" has a quite detailed introduction.

In V8, all JavaScript objects are allocated memory through the "heap".

JavaScript also talks about memory optimization_javascript skills

When we declare a variable in the code and assign a value, V8 will allocate a part of the heap memory to the variable. If the allocated memory is not enough to store this variable, V8 will continue to apply for memory until the heap size reaches the V8 memory limit. By default, the upper limit of V8's heap memory size is 1464MB in 64-bit systems and 732MB in 32-bit systems, which is about 1.4GB and 0.7GB.

In addition, V8 manages JavaScript objects in heap memory by generation: new generation and old generation. The new generation refers to JavaScript objects with a short life cycle, such as temporary variables, strings, etc.; while the old generation refers to objects that have survived multiple garbage collections and have a long life cycle, such as main controllers, server objects, etc.

Garbage collection algorithms have always been an important part of the development of programming languages, and the garbage collection algorithms used in V8 mainly include the following:

1. Scavange algorithm: memory space management through copying, mainly used for the memory space of the new generation;
2. Mark-Sweep algorithm and Mark-Compact algorithm: organize and organize the heap memory through marking Recycling is mainly used for inspection and recycling of old generation objects.


PS: More detailed V8 garbage collection implementation can be learned by reading relevant books, documents and source code.

Let’s take a look at which objects the JavaScript engine will recycle under what circumstances.

2.1 Scope and Reference

Beginners often mistakenly believe that when the function completes execution, the object declared inside the function will be destroyed. But in fact, this understanding is not rigorous and comprehensive, and it can easily lead to confusion.

Reference is a very important mechanism in JavaScript programming, but the strange thing is that most developers don’t pay attention to it or even understand it. Reference refers to the abstract relationship of "code's access to objects". It is somewhat similar to C/C pointers, but they are not the same thing. References are also the most critical mechanism for garbage collection by the JavaScript engine.

Take the following code as an example:

Copy the code The code is as follows:

// . .....
var val = 'hello world';
function foo() {
return function() {
return val;
};
}
global.bar = foo();
// ......

After reading this code, can you tell which objects are still alive after this part of the code is executed?

According to relevant principles, the objects in this code that have not been recycled include val and bar(). What is the reason why they cannot be recycled?

How does the JavaScript engine perform garbage collection? The garbage collection algorithm mentioned earlier is only used during recycling, so how does it know which objects can be recycled and which objects need to continue to survive? The answer is a reference to a JavaScript object.

In JavaScript code, even if you simply write a variable name as a separate line without doing any operation, the JavaScript engine will think that this is an access behavior to the object, and there is a reference to the object. In order to ensure that the behavior of garbage collection does not affect the operation of program logic, the JavaScript engine must not recycle the objects being used, otherwise it will be chaotic. So the criterion for judging whether an object is in use is whether there is still a reference to the object. But in fact, this is a compromise, because JavaScript references can be transferred, then some references may be brought to the global scope, but in fact there is no need to modify them in the business logic. Once accessed, it should be recycled, but the JavaScript engine will still rigidly believe that the program still needs it.

How to use variables and references in the correct manner is the key to optimizing JavaScript from the language level.

3. Optimize your JavaScript

Finally getting to the point. Thank you very much for your patience in reading this. After all the above introductions, I believe you already have a good understanding of JavaScript’s memory management mechanism. Then the following techniques will make you even more powerful.

3.1 Make good use of functions

If you have the habit of reading excellent JavaScript projects, you will find that when many experts develop front-end JavaScript code, they often use an anonymous function to wrap the outermost layer of the code.

Copy code The code is as follows:

(function() {
// Main business Code
})();

Some are even more advanced:
Copy code The code is as follows :

;(function(win, doc, $, undefined) {
// Main business code
})(window, document, jQuery);

Even front-end modular loading solutions such as RequireJS, SeaJS, OzJS, etc. adopt a similar form:
Copy code Code As follows:

// RequireJS
define(['jquery'], function($) {
// Main business code
});

// SeaJS
define('module', ['dep', 'underscore'], function($, _) {
// Main business code
});

If you say that the code of many Node.js open source projects is not handled in this way, then you are wrong. Before actually running the code, Node.js will package each .js file into the following form:
Copy code The code is as follows:

(function(exports, require, module, __dirname, __filename) {
// Main business code
});

What are the benefits of doing this? We all know that as mentioned at the beginning of the article, the scopes in JavaScript include function calls, with statements and global scope. And we also know that objects defined in the global scope are likely to survive until the process exits. If it is a large object, it will be troublesome. For example, some people like to do template rendering in JavaScript:

Copy the code The code is as follows:
 
  $db = mysqli_connect(server, user, password, 'myapp');
  $topics = mysqli_query($db, "SELECT * FROM topics;");
?>



 
  你是猴子请来的逗比么?


 

     
     

    这种代码在新手的作品中经常能看得到,这里存在什么问题呢?如果在从数据库中获取到的数据的量是非常大的话,前端完成模板渲染以后,data变量便被闲置在一边。可因为这个变量是被定义在全局作用域中的,所以JavaScript引擎不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。

    可是如果我们作出一些很简单的修改,在逻辑代码外包装一层函数,这样效果就大不同了。当UI渲染完成之后,代码对data的引用也就随之解除,而在最外层函数执行完毕时,JavaScript引擎就开始对其中的对象进行检查,data也就可以随之被回收。

    3.2 绝对不要定义全局变量

    我们刚才也谈到了,当一个变量被定义在全局作用域中,默认情况下JavaScript 引擎就不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。

    那么我们就一直遵循一个原则:绝对不要使用全局变量。虽然全局变量在开发中确实很省事,但是全局变量所导致的问题远比其所带来的方便更严重。

    使变量不易被回收;
    1.多人协作时容易产生混淆;
    2.在作用域链中容易被干扰。
    3.配合上面的包装函数,我们也可以通过包装函数来处理『全局变量』。

    3.3 手工解除变量引用

    如果在业务代码中,一个变量已经确切是不再需要了,那么就可以手工解除变量引用,以使其被回收。

    复制代码 代码如下:

    var data = { /* some big data */ };
    // blah blah blah
    data = null;

    3.4 善用回调

    除了使用闭包进行内部变量访问,我们还可以使用现在十分流行的回调函数来进行业务处理。

    复制代码 代码如下:

    function getData(callback) {
    var data = 'some big data';

    callback(null, data);
    }

    getData(function (err, data) {
    console.log(data);

    The callback function is a Continuation Passing Style (CPS) technology. This style of programming shifts the business focus of the function from the return value to the callback function. And it has many advantages over closures:

    1. If the parameters passed in are basic types (such as strings, numeric values), the formal parameters passed in the callback function will be copied values. After the business code is used, it is easier to recycle;
    2 .Through callbacks, in addition to completing synchronous requests, we can also use it in asynchronous programming, which is a very popular writing style now;
    3. The callback function itself is usually a temporary anonymous function. Once requested After the function is executed, the reference of the callback function itself will be released and itself will be recycled.

    3.5 Good closure management

    When our business requirements (such as loop event binding, private properties, parameter-containing callbacks, etc.) must use closures, please be careful about the details.

    Loop binding events can be said to be a required course for getting started with JavaScript closures. We assume a scenario: there are six buttons, corresponding to six types of events. When the user clicks the button, the corresponding event is output at the specified place.


    Copy code The code is as follows:

    var btns = document.querySelectorAll('. btn'); // 6 elements
    var output = document.querySelector('#output');
    var events = [1, 2, 3, 4, 5, 6];

    // Case 1
    for (var i = 0; i < btns.length; i ) {
    btns[i].onclick = function(evt) {
    output.innerText = 'Clicked ' events [i];
    };
    }

    // Case 2
    for (var i = 0; i < btns.length; i ) {
    btns[i] .onclick = (function(index) {
    return function(evt) {
    output.innerText = 'Clicked ' events[index];
    };
    })(i);
    }

    // Case 3
    for (var i = 0; i < btns.length; i ) {
    btns[i].onclick = (function(event) {
    return function(evt) {
           output.innerText = 'Clicked ' event;
        };
       })(events[i]);
    }

    The first solution here is obviously a typical loop binding event error. I won’t go into details here. For details, you can refer to the answer I gave to a netizen; and the difference between the second and third solutions lies in the incoming closure. parameters.

    The parameter passed in in the second solution is the current loop subscript, while the latter directly passes in the corresponding event object. In fact, the latter is more suitable for large-scale data applications, because in JavaScript functional programming, the parameters passed in when the function is called are basic type objects, so the formal parameters obtained in the function body will be a copied value, so This value is defined as a local variable in the scope of the function body. After completing the event binding, the events variable can be manually dereferenced to reduce the memory usage in the outer scope. And when an element is deleted, the corresponding event listening function, event object, and closure function are also destroyed and recycled.

    3.6 Memory is not a cache

    Caching plays an important role in business development and can reduce the burden of time and space resources. But it should be noted that do not use memory as a cache easily. Memory is a valuable resource for any program development. If it is not a very important resource, please do not place it directly in the memory, or develop an expiration mechanism to automatically destroy the expired cache.

    4. Check JavaScript memory usage

    In daily development, we can also use some tools to analyze and troubleshoot memory usage in JavaScript.

    4.1 Blink / Webkit Browser

    In Blink/Webkit browsers (Chrome, Safari, Opera etc.), we can use the Profiles tool of Developer Tools to check the memory of our program.

    JavaScript also talks about memory optimization_javascript skills
    4.2 Memory Checking in Node.js

    In Node.js, we can use node-heapdump and node-memwatch modules for memory checking.

    Copy code The code is as follows:

    var heapdump = require('heapdump');
    var fs = require('fs');
    var path = require('path');
    fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid);
    // ...

    複製程式碼 代碼如下:
    ', 'Bitstream Charter', Times, serif; font-size: 14px; line-height: 1.5em;">在業務程式碼中引入node-heapdump 之後,我們需要在某個運行時期,向Node.js 進程發送SIGUSR2 訊號,讓node-heapdump 抓拍一份堆記憶體的快照。
    程式碼如下:
    $ kill -USR2 (cat app. pid)

    這樣在文件目錄下會有一個以heapdump-..heapsnapshot格式命名的快照文件,我們可以使用瀏覽器的Developer Tools中的Profiles工具將其打開,並進行檢查。

    5. 小結

    很快又來到了文章的結束,這篇分享主要向大家展示了以下幾點內容:


    1.JavaScript 在語言層面上,與記憶體使用息息相關的東西;
    2.JavaScript 中的記憶體管理、回收機制;
    3.如何更有效率地使用內存,以至於讓出產的JavaScript能更有拓展的活力;

    4.如何在遇到記憶體問題的時候,進行記憶體檢查。


    希望透過這篇文章的學習,你能夠出產更優秀的JavaScript 程式碼,讓媽媽安心、讓老闆放心。

    Statement:
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn