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:
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.
As shown in the following code:
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.
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:
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".
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:
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.
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:
这种代码在新手的作品中经常能看得到,这里存在什么问题呢?如果在从数据库中获取到的数据的量是非常大的话,前端完成模板渲染以后,data变量便被闲置在一边。可因为这个变量是被定义在全局作用域中的,所以JavaScript引擎不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。
可是如果我们作出一些很简单的修改,在逻辑代码外包装一层函数,这样效果就大不同了。当UI渲染完成之后,代码对data的引用也就随之解除,而在最外层函数执行完毕时,JavaScript引擎就开始对其中的对象进行检查,data也就可以随之被回收。
3.2 绝对不要定义全局变量
我们刚才也谈到了,当一个变量被定义在全局作用域中,默认情况下JavaScript 引擎就不会将其回收销毁。如此该变量就会一直存在于老生代堆内存中,直到页面被关闭。
那么我们就一直遵循一个原则:绝对不要使用全局变量。虽然全局变量在开发中确实很省事,但是全局变量所导致的问题远比其所带来的方便更严重。
使变量不易被回收;
1.多人协作时容易产生混淆;
2.在作用域链中容易被干扰。
3.配合上面的包装函数,我们也可以通过包装函数来处理『全局变量』。
3.3 手工解除变量引用
如果在业务代码中,一个变量已经确切是不再需要了,那么就可以手工解除变量引用,以使其被回收。
3.4 善用回调
除了使用闭包进行内部变量访问,我们还可以使用现在十分流行的回调函数来进行业务处理。
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.
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.
4.2 Memory Checking in Node.js
In Node.js, we can use node-heapdump and node-memwatch modules for memory checking.
這樣在文件目錄下會有一個以heapdump-
5. 小結
很快又來到了文章的結束,這篇分享主要向大家展示了以下幾點內容:
1.JavaScript 在語言層面上,與記憶體使用息息相關的東西;
2.JavaScript 中的記憶體管理、回收機制;
3.如何更有效率地使用內存,以至於讓出產的JavaScript能更有拓展的活力;
希望透過這篇文章的學習,你能夠出產更優秀的JavaScript 程式碼,讓媽媽安心、讓老闆放心。