Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript的内存模型 #28

Open
Vibing opened this issue Aug 11, 2020 · 0 comments
Open

JavaScript的内存模型 #28

Vibing opened this issue Aug 11, 2020 · 0 comments

Comments

@Vibing
Copy link
Owner

Vibing commented Aug 11, 2020

内存的生命周期

JavaScript作为一门高级编程语言,不像其他语言(例如C语言)需要开发者手动的去管理内存,在 JavaScript 中,系统会自动为你分配内存,在几乎任何一种语言中,内存的生命周期主要分三个阶段:

  • 内存分配:一般由操作系统分配内存,在有的语言中需要开发者手动操作
  • 使用内存:获得操作系统分配的内存后,在内存中发生读和写的操作
  • 释放内存:在程序使用完内存后,会将这部分内存释放掉供其他程序使用,在 JavaScript 中这一步由垃圾回收机制自动释放

栈内存和堆内存

JavaScript数据类型分两大类:基本类型、引用类型。
字符串、数字、布尔值等属于基本类型,对象类型都属于引用类型。

是一种先进后出、后进先出的数据结构,
举个例子,乒乓球盒子(先进后出,后进先出):

是一种树的结构,所以它是“无序”的,可以从任何地方将数据取出来,比如书架里的书

// 基本类型
const a = 'hello world';

// 引用类型
const obj = {}

栈内存中存的数据大小必须是固定的,所以基本类型都会存储在栈内存中,这种存储方式属于简单存储,也叫静态内存,所以它的效率是很高的。

堆内存中存的是引用类型的对象数据,比如 JSON、Function、Array 等等,因为引用类型数据没有固定大小,所以不能存在于栈内存,在运行时的访问方式是通过存在栈内存里的指针(地址)去堆里寻找对应的对象数据,也称它为动态内存,堆内存的效率是比栈内存要低。

我们在使用引用类型数据时,系统会在栈内存中存一个地址,该地址指向堆内存空间中你需要找的对象。

内存泄漏

常听说内存泄漏,到底什么是内存泄漏? 从本质上说,内存泄漏可以定义为:不再被应用程序所需要的内存,出于某种原因,它不会被GC回收

意想不到的全局变量

在 JavaScript 中,对于没有声明的变量,则会在全局范围中创建一个新的变量并对其进行引用,在浏览器中,全局对象是 window,例如:

function foo(arg) {
    bar = "some text";
    this.book = 'JavaScript';
}

等价于:

function foo(arg) {
    window.bar = "some text";
    window.book = 'JavaScript'
}

如果这种意外的全局变量过多,则会导致内存溢出

你可以使用更严格的use strict模式来避免它,或者你必须能确保将其指定为null

被遗忘的定时器和回调

常用的定时器,比如setInterval

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); //每五秒会执行一次

定时器里有对 id 为 renderer 的 DOM 进行引用,试想如果未来在某处将该 DOM 删掉,会导致定时器内部的代码变得不再需要,但由于定时器仍在运行,所以导致里面的处理程序所占内存不能被 GC 回收,也意味着 serverData 不能被回收。

所以我们在不适用定时器的时候,务必使用clearInterval将该定时器清除,断开其内部代码的引用,可以让 GC 收集和回收。

闭包的使用

闭包是内部函数使用外部函数的变量

let thing = null;

const fun1 = function () {
    const oThing = thing;
    const unused = function () {
        if(oThing) // 引用了oThing
            console.log('hi')
    }
    
    thing = {
        longStr: new Array(10000).join('*'),
        someMethod: function (){
            console.log('message')
        }
    }
}

setInterval(fun1, 1000)

unused函数内引用了外部的oThing,在fun1中调用了thing
thing中,为 someMethod 创建的作用域可以被 unused 共享。

该闭包的形成,阻止了oThing的回收。

而且在定时器中被循环执行,thing的内存大小将会稳定增长,而且每个作用域都间接引用了一个longStr这个大数组,造成了相当大的内存泄漏。

DOM的引用

const elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image')
}

function setSrc() {
    elements.image.src = 'http://exmaple.com/image_name.jpg'
}

function removeImage(){
    // image是body元素的直接子元素
    document.body.removeChild( document.getElementById('image') )
    // 此时,虽然 image 元素被删了,但全局对象中仍然引用#image,仍在内存中,GC无法收集它
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant