preface

A product manager recently told me that one of the company’s systems was constantly switching the main menu, and the browser’s memory usage was skyrocketing. There was a first-reaction memory leak. After replaying the scene, it was found that the memory occupied by the browser doubled when the main menu of A was switched to the main menu of B and the browser was switched back to the main menu of A. It was basically confirmed that there was A memory leak.

First, the investigation process

According to the BUG scenario, the browser memory usage is doubled, we can initially determine that the Vue component is not released.

In Vue, if a Vue component has a memory leak, components that reference that component cannot be destroyed. Why would that be?

The first thing to understand is that Vue components will eventually be rendered as a DOM tree, such as a table component consisting of multiple cell components, if you keep a reference to a cell in your JS code. In the future you decide to remove the table from the DOM and keep the reference to the cell. You might think that GC would reclaim everything except this cell, but it doesn’t. A cell is a child node of the table and all child nodes retain references to their parents. In other words, a reference to a cell in the JS code causes the entire table to be kept in memory.

So if you use the Chrome Developer Tools Memory function, you’ll find out how every component has a Memory leak.

At this time, we need to use the elimination method to find out the leak point. As mentioned before, memory leakage occurs when switching the main menu. First, annotate the contents of each menu page, and then switch the main menu.

Open notes for each component in the main menu page of A and switch to the main menu of A and B for testing. Fortunately, the leak point was found. The Split panel Split component had memory leakage.

In order to exclude other influences, a separate test project was set up. The Split panel in iView was introduced to Split components and switch routes, and memory leakage was found.

View project iView is 3.4.2 version, on GitHub to view the source code.

In the Mounted hook function, there is a listener for the Window binding resize, but it is not unbound in the beforeDestroy hook function. Copy this component code into the test project and change it to

methods:{
    handleUp () {
        this.isMoving = false;
        document.removeEventListener('mousemove',this.handleMove,false);
        document.removeEventListener('mouseup',this.handleUp,false);
        this.$emit('on-move-end');
    },
    handleMousedown (e) {
        this.initOffset = this.isHorizontal ? e.pageX : e.pageY;
        this.oldOffset = this.value;
        this.isMoving = true;
        document.addEventListener('mousemove',this.handleMove,false);
        document.addEventListener('mouseup',this.handleUp,false);
        this.$emit('on-move-start');
    },
    computeOffset(){
        this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100;
    }
},
watch: {
    value () {
        this.computeOffset();
    }
},
mounted () {
    this.$nextTick(() => {
        this.computeOffset();
    });
    window.addEventListener('resize',this.computeOffset);
},
beforeDestroy(){
    window.removeEventListener('resize',this.computeOffset);
}
Copy the code

Retest and find that the memory leak disappeared

Back to the original project, the Split panel component in iView was replaced with the modified component. After testing, no memory leakage occurred.

Two, about removing the event listening pit

There is code for this in the Split panel Split component in iView

mounted () {
    this.$nextTick(() => {
        this.computeOffset();
    });
    window.addEventListener('resize', ()=>{
        this.computeOffset();
    });
}
Copy the code

How do I remove this event listener in beforeDestroy

beforeDestroy(){
    window.removeEventListener('resize', ()=>{
        this.computeOffset();
    });
}
Copy the code

or

beforeDestroy(){
    window.removeEventListener('resize',this.computeOffset)
}
Copy the code

After testing, you will find that the memory leak is still not resolved. You will wonder why there is still a memory leak even though the event listening has been removed. In fact, there is a pit on this side.

If you want to remove event listening, the following points must be met

  1. The executing function of addEventListener() must use an external function, for example:

Window. The addEventListener (‘ resize, this.com puteOffset); 2. The function cannot be an anonymous function, for example: window.addeventListener (‘resize’, ()=>{this.puteoffset ()}); This.puteoffset.bind (this)); this.puteoffset.bind (this); 4. The third parameter of addEventListener must be the same as that of removeEventListener, for example: window.addEventListener(‘resize’,this.computeOffset,true); window.removeEventListener(‘resize’,this.computeOffset,true);

``` window.addEventListener('resize',this.computeOffset,false); window.removeEventListener('resize',this.computeOffset,false); ` ` `Copy the code