6
头图

foreword

Hello everyone, I'm Lin Sanxin. In the last article, I told you that gave you 13 pictures, which helped you defeat the "V8 garbage collection mechanism" 20 minutes, but Guan knows that the recycling mechanism is not good, V8 garbage collection The mechanism is very strong, but we can’t just create a lot of garbage for recycling. We have to reduce the amount of garbage as much as possible during development. Today, I will tell you how to avoid too much garbage in

why avoid

What is the memory leak? That is, some garbage that should be recycled is not recycled, which causes the accumulation of garbage.

memory leak sounds far away, but it is actually very close to us, and we have been in contact with it directly or indirectly. For example, sometimes your page gets stuck when you use it, and it gets more and more stuck as time by. this time, you need to consider whether it is a 161e36e192494b memory leak problem. 161e36e192494d memory leak affects the user experience. It is a major problem, so it is very necessary to avoid it through correct coding habits.

How to Monitor Memory Status

We have been emphasizing memory and memory, but I feel that it is a very illusory thing, at least let us see its true colors, right?

Browser Task Manager

Open: In the top of the browser right, open Task Manager:

截屏2021-08-03 下午10.15.23.png

After opening, we see memory and JavaScript memory (in parentheses):

  • memory: the original memory in the page, that is, the total memory occupied by DOM nodes
  • JavaScript memory (in parentheses): is the total memory occupied reachable objects in the page

So what is a reachable object? Simply put: it starts from root object (window or global), and searches down the child node. If the child node is searched, it means that the reference object of the child node is reachable. If it cannot be found, it means the child node. Object is not reachable. for example:

// 可达,可以通过window.name访问
var name = '林三心'

function fn () {
    // 不可达,访问不了
    var name = '林三心'
}

Back to our task management, at this time we write a piece of code in the page:

<button id="btn">点击</button>
<script>
    document.getElementById('btn').onclick = function () {
        list = new Array(1000000)
    }
</script>

Before clicking:

截屏2021-08-03 下午10.16.50.png

After clicking, it is found that the memory rises instantly:

截屏2021-08-03 下午10.17.18.png

Performance

Using the incognito mode of the Chrome browser is to avoid many other factors that affect our view of memory:

截屏2021-08-03 下午10.39.58.png

Press F12 to open the debug window and select Performance

image.png

Let's take the Nuggets homepage as an example! Click Record -> Refresh Nuggets -> Click stop , you can see the following indicators fluctuate up and down :

  • JS Heap : JS heap
  • Documents : Documentation
  • Nodes : DOM node
  • Listeners : Listener
  • GPU Memory : GPU memory
    juejinperf.gif

heap snapshot

heap snapshot, as the name suggests, is to take a picture heap memory of a current page and save it. For the same page, before performing an operation, the recorded heap snapshot is the same. It is possible that after the execution, the recorded heap snapshot will be different. It's another kind.

image.png

Or take the Nuggets homepage as an example, you can see that the current page memory is 13.3M , we can choose Statistics , and view the memory occupied by arrays, objects, strings, etc.

掘金堆快照.gif

how to avoid

As mentioned above, in fact, the memory leak problem is very close to us, and we may have caused it directly or indirectly. Next, let's talk about how to avoid this problem, it may also be a bad habit in your development!

reduce global variables

We may have encountered such code in development. In fact, we just want to use a as a local variable, but forgot to write var,let,const :

document.getElementById('btn').onclick = function () {
    // a 未在外部声明过
    a = new Array(1000000).fill('Sunshine_Lin')
}

上方代码等同于
var a
document.getElementById('btn').onclick = function () {
    a = new Array(1000000).fill('Sunshine_Lin')
}

What's the downside? We mentioned reachability earlier, which can be explained here. If the above code is written like this, we can access the global variable a window.a , so a is reachable, and it will not be collected as garbage, which will cause it to keep occupying memory and not be released, consuming performance , contrary to our original intention. We can heap snapshot. The steps are: record -> click the button -> record, compare the results twice, after clicking, the memory is larger by 4M , check Statistics , and find that the array memory is much larger and has not been released :

全局变量堆快照.gif

How should it be improved? You can add the define variable operator:

document.getElementById('btn').onclick = function () {
     let a = new Array(1000000).fill('Sunshine_Lin')
}

Looking at the effect, due to local variables, is unreachable. Every time a function is executed, it will be reclaimed and released, so it will not occupy the memory all the time. The memory before and after the click is similar:

局部变量堆快照.gif

Timer not cleared

Please look at this piece of code. In this code, after executing the fn1 function, it stands to reason that the arr array will be recycled, but it cannot be recycled. why? Because a in the timer refers to arr, and if the timer is not cleared, a will not be recycled. a not recycled, it will always refer to arr , so arr definitely not be recycled.

function fn() {
      let arr = new Array(1000000).fill('Sunshine_Lin')
      setInterval(() => {
          let a = arr
     }, 1000)
}
document.getElementById('btn').onclick = function () {
    fn()
}

Performace: Recording -> Manual Garbage Collection -> Click the button five times in a row -> Manual Garbage Collection -> End

The first and last two manual garbage collections are to compare the lowest points of garbage memory in the first and last two times. If there is no memory leak problem, the two lowest points should be the same. Here you can see that the tail is more than the head. The part, is the amount of memory that has not been reclaimed
定时器perf.gif

As mentioned above, why is the arr array not recycled? It is because the timer is not cleared, so that a keeps referencing arr . How to solve it? clear the 161e36e192510d timer directly.

function fn() {
  let arr = new Array(1000000).fill('Sunshine_Lin')
  let i = 0
  let timer = setInterval(() => {
    if (i > 5)  clearInterval(timer)
    let a = arr
    i++
  }, 1000)
}
document.getElementById('btn').onclick = function () {
  fn()
}

Look at Performance , and find that the amount of memory in the first two times is the same, which means that it is normal

清除定时器perf.gif

Use closures wisely

Let's look at this piece of code:

function fn1() {
    let arr = new Array(100000).fill('Sunshine_Lin')

    return arr
}
let a = []
document.getElementById('btn').onclick = function () {
    a.push(fn1())
}

It stands to reason that fn1 executed, arr will be recycled, but in this code, it is not recycled, why? Because fn1 After execution, the arr to return out, then arr is into a array of push, but the array is a global variable, a array will not be recovered, then a natural array of things will not be Recycling, this will lead to arr will not be recycled. After more and more clicks, there will be more and more arr a not used later, then these arr will become useless garbage, let's This can be verified with Performance and heap snapshots:

Performace: Recording -> Manual Garbage Collection -> Click the button five times in a row -> Manual Garbage Collection -> End

The first and last two manual garbage collections are to compare the lowest points of garbage memory in the first and last two times. If there is no memory leak problem, the two lowest points should be the same. Here you can see that the tail is more than the head. The part, is the amount of memory that has not been reclaimed

闭包perfo.gif

heap snapshot: first recording -> click the button 5 times in a row -> second recording

You will find that before and after the click, there is a lot more memory, and the extra memory is the amount of memory that has not been reclaimed.

闭包堆快照.gif

Detach the DOM

What is the separation DOM? Or use code to speak:

<button id="btn">点击</button>

let btn = document.getElementById('btn')
document.body.removeChild(btn)

Although the button was finally deleted, because the global variable btn to the DOM object, the DOM object has not been recycled. This DOM object is called the detached DOM. We can verify this heap snapshot. Problem, search for detached (Chinese meaning: independent, detached) in the heap snapshot:

分离DOM堆快照.gif

This problem is easy to solve. After deleting the button, set the btn to null by the way:

<button id="btn">点击</button>

let btn = document.getElementById('btn')
document.body.removeChild(btn)
btn = null

At this time, the button DOM is really completely erased from js:

分离domnull.gif

References

Epilogue

I'm Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends and fish together haha, touch the fish group, add me, please note [Si No]

image.png


Sunshine_Lin
2.1k 声望7.1k 粉丝