😔javascript
如下用一段代码说明堆和栈的区别:html
后进先出结构前端
早高峰的电梯,挤满了人,先进去的要想出来,后进去的是否是要先出来让路?就是这个道理吧。。。java
这样,要获取其中一个,是否是很费性能。node
存放的数据类型:c++
String、Number、Boolean、Null、Undefined 这五种基础数据类型。程序员
拷贝这些类型的数据就是拷贝一个副本面试
以及:算法
Object、Array、Function等引用类型的指针。数组
拷贝这些类型的数据是拷贝了指针一个副本,新指针和原指针仍是指向堆内存里的同一个地址。
队列是先进先出结构,它两边都有口。就像去火车站排队买票。第一我的先排队的,业务员第一个接待他。(业务员就是js主线程)
树状结构
能够随时获取,就像书架上的书,也像苹果树上的每个苹果,想摘那个摘哪一个。就能够省点力气(不像栈,想摘最高的那个,还得把最底下的摘完才能摘。。。)
存放的数据类型:
Object、Array、Function等引用类型、闭包的变量。
通常归类到栈中,存放常量。
和学到这里的你同样,我常常搞不懂栈和调用栈究竟是真么关系?栈空间和栈内存又是否是一回事儿?
直到我拜读了阮一峰老师的博文,才渐渐清醒起来。
这里记录本身的读书笔记。想观摩原做的请跳转至:Stack的三种含义
做为一种数据的存放方式,特色是后进先出。
上边电梯的例子在这里不够更加形象,
能够想象成一摞叠在一块儿的一元硬币,假如一共十个,想要拿出最下边那个,计算机的处理方式就是,把上边九个依次拿开,
才能把第十个拿出来给你(不要想一块儿把前九个抬起来拿最后一个的想法,那是你不是计算机。。。)。
这种作法,在js中有些方法组合使用就是了:
push + pop:从最后边依次推动去,再从最后边拿。上边的1就是push(),2就是pop()的作法了。
调用栈,每个函数在调用的时候,都堆叠到一块儿(和上边的硬币同样),须要把上边的先调用了,而后销毁、出栈以后,再把下边的暴露出来进行。
好比下边这段代码的执行:
这里,foo()和bar()【包括window在js代码】运行时,js引擎就会生成执行上下文(就是我们常说的函数做用域了),这一段理解了,就能绕过不少面试题的障碍。
后期会整理到这里。等不及的你赶忙去看看吧。
存放数据的一种内存区域。
系统划分出来的两个内存空间:栈和堆
区别如上所说,
栈有结构,因此要按次序存放,也能够知道每一个区的大小,超过大小就是栈溢出错误。一个线程分配一个stack,线程独占。运行结束栈被清空
堆没有结构,能够任意存放,大小也不能肯定,能够按须要增长。每一个进程分配一个heap,线程共用。运行结束对象实例继续存在,直到垃圾回收。若是没被回收,就是内存泄漏。
(stack的寻址速度快于heap?)
就是咱们声明一个变量、对象等的时候,系统就会自动给咱们的变量分配内存。
好比执行下边的代码:
var a = 10;
事实上,编译器会这么处理:(节选自《你不知道的js(上)》第一章 1.2.2 p7)
遇到 var a,编译器会先像做用域(由于这里是在全局做用域执行的,你能够理解为window)寻找是否已经有一个a存在同一个做用域集合(window对象)中。
若是有a,则忽略该声明,继续进行编译。
不然没有a,会要求做用域在当前做用域集合(即window对象)中声明一个新的变量,并命名为a。这个过程,就是内存分配。
就是编译器读、写内存,调取变量/对象等的值的时候。
读就是获取变量值,写入就是赋值或修改变量的值。这里引入两个《你不知道的js(上)》介绍的名词
好比:
console.log(a)
引擎在这里会有两段RHS查找(获得某某的值):
一、查找console
二、查找a的值
首先,console这个对象是在window对象上的一个属性。log是他上边的一个方法。
而后查找a的值,由于a被建立到了栈内存,对a的取值就是内存使用
当咱们使用完一个函数,该函数就会被自动销毁。(不考虑闭包的状况)
js中有垃圾回收机制,会自动回收再也不使用的内存。
var a = null;//使用完毕,自动释放内存空间
不少语言,在使用完毕后须要程序员手动释放内存,咱们应该庆幸的是,js引擎有自动的垃圾回收机制。能够自动进行内存管理,减轻了咱们的工做负担。
垃圾收集器每隔固定的时间就执行一次检查,再也不使用的值就释放其占用的内存。
那么问题是,垃圾回收机制怎么知道哪些内存须要回收了呢?
引用计数 方法:
原理,就是引擎记录全部对象值的引用次数,若是引用次数是0,就表示没用了能够“删除”
那什么样才算没有引用了呢?
好比这里js文件中只有一行代码:
var a = [123,2];
你说a有引用吗?
我第一感受是没有的,可是看阮一峰大神的讲解,这里是还有引用的。
数组还在占用内存,变量a是一个引用。因此数组[123,2]这个变量值的引用次数是1,不能被清除
要想解除他的引用,须要执行
a = null;这样,a变成了一个null值,而数组[123,2]没有人引用他了,下一轮垃圾清理器过来的时候就会把他清除了。
因此,这么看来,咱们写完程序后还要检查有哪些值是再也不使用的,就给他指向null,剪断引用的线,释放内存空间。
尤为是在全局做用域。由于局部函数做用域有可能在没有闭包的状况下,函数执行完毕就会被自动消除。可是全局的变量,系统很难判断还有没有用,就像上边的a同样,因此咱们除了避免使用全局变量外,还有记得及时释放不得已创建的全局变量。
可是他也有他的缺点,我上边列了脑图。
出现循环引用的状况:
var div = document.createElement("div"); div.onclick = function() { console.log("click"); };
变量div有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。一个循序引用出现了,按ie中用的引用计数算法,该部份内存无可避免地泄露了。
扩展:
ie8中,COM对象,用c++实现的组件对象模型,使用的就是引用计数方法。经常由于循环引用发生内存泄漏
标记清除 方法:(经常使用)
原理:对象是否可达。否,则被回收
从window全局对象根对象开始遍历,按期向下查找,找全部从根开始引用的对象、这些对象引用的对象。而后就知道哪些是可达到的,哪些是不可达到的(个人理解是和其余人没有联系的)
能达到的添加标识,最后没有标识的就会被内存回收,而且将以前的标记清除,下一次从新标记
这样,在循环引用的状况中,即便两者彼此互帮互助循环引用防止垃圾清除,可是,标记清除法则从根元素开始找,找不到他俩,他俩就都被清除了。
2018-12-07 23:40:48
前文说道,若是我们创建的变量对象在不使用时没有及时被回收,就会形成内存泄漏。
就是动态分配的空间,在使用完毕后没有被释放,就会致使该内存空间一直被白白占用,直到程序结束。
危害:
想一想,电脑的空间就那么多,一直有人“占着茅坑不拉*”,内存空间就会愈来愈少,程序运行就会愈来愈慢,形成程序的卡顿效果,严重的甚至还可能致使系统的崩溃。
具体表现整理以下:(参见搜狗百科)
一、cpu资源耗尽
鼠标键盘等操做无反应、页面假死
二、进程耗尽
没有新的进程能够用了,放到浏览器里有Browser进程、插件进程、GPU进程、内核渲染进程等,若是进程耗尽,其中想开一个是否是就不可能了。
三、硬盘耗尽
机器崩溃
四、内存泄漏或者内存耗尽
很麻烦并且很差用工具定位和跟踪 - 隐式内存泄漏
内存泄漏的分类:
常发性
偶发性
一次性
隐式:
说说这个和咱们前端有关系的隐式内存泄漏,就是程序自动给咱们的变量分配了内存空间,可是直到程序结束,他们才被释放。虽然程序结束,释放了全部内存,可是若是长时期的不结束这段程序,内存也就不能及时释放。
高级前端进阶公众号文章阅读笔记
目录:
一、意外的全局变量
二、被遗忘的定时器或回调函数
三、脱离DOM的引用
四、闭包
一、意外的全局变量
在函数做用域中,未使用var定义的变量会在全局建立一个新的全局变量:
function foo(){ a = 2;
var b = '局部变量' }
此时,a没有使用var定义,就会在全局对象中建立一个a属性。
或者,在普通函数中使用this时,也就在全局对象window上建立一个属性:
function bar(){
this.a = 2;
}
这种作法和上边定义那个a没什么区别(固然,在不使用new bar构造函数的状况下),this指向window(非严格模式下);
由于若是使用严格模式,this指向的是undefiend;
解决方法就是:
尽可能变量都定义在局部函数做用域中,而且记得使用var等变量声明一下。
另外,若是不是使用构造函数,普通函数内部也记得使用this的时候,js的文件头部加上"use strict"字样,表示使用严格模式编译。
若是必须使用全局变量,那么要确保使用完之后将该变量指向null或重定义。
定时器setInterval
var a = fun();
setInterval(function(){
var node = document.getElementById('node');
if(node){
node.innerHTML = 'test';
}
},1000);
上例:节点node或数据不须要时,定时器setInterval依然指向这些数据,因此即便node节点被溢出,interval仍旧存活而且垃圾回收期无法回收。
解决是终止定时器。
这种循环定时器,是必定要设置关闭条件,而后将其clear而且将timer指向null
三、脱离DOM的引用:
若是把DOM存成字典(键值对)或者数组,此时,一样的dom元素存在两个引用:
一个在DOM树中,另外一个在字典中,那么未来须要把两个引用都清除。
好比:
var elements ={
btn: document.getElementById('button'),
img: document.getElementById('image'),
};
function doS(){
elements.img.src = 'test/img.png';
elements.btn.click();
}
function removeBtn(){
document.body.removeChild(document.getElementById('button'));
}
//此时,仍旧存在一个全局的#button的引用,
//elements字典,btn元素仍旧在内存中,不能被回收
若是代码中保存了表格某一个<td>的引用,未来决定删除整个表格的时候,,你会认为回收器会回收除了已保存的<td>之外的其余节点?
事实上:<td>是表格的子节点,子元素和父元素是引用关系,因为代码保留了<td>的应用
致使整个表格仍待在内存中,因此保存DOM元素引用的时候要当心谨慎。
四、闭包
闭包的关键是匿名函数能够访问父级做用域的变量。
咱们知道,函数在调用完毕以后,会被抛出执行栈进行销毁,且函数内部的局部变量也就不存在的。
可是若是有闭包的存在,函数被抛出执行栈之后,因为闭包内部引用了父级函数做用域内部的局部变量,
这些变量就不会被销毁,而是继续占据着内存空间,严重时形成泄漏。这是闭包的特性,但也是他的缺点。
一样的,能够不用的时候指向null
(高程3)一旦肯定数据再也不使用,能够手动将其值设置为null来释放其引用。 —— 解除引用。
此作法适用于全局变量和全局对象的属性。由于局部变量大多会在他们离开执行环境时自动被解除引用。
其余的对照第六点中的对应状况寻找对应解决方法吧。
引用阅读:
【1】《阮一峰的网络日志》 博客相关文章
【2】《高级前端进阶》 公众号相关文章
【3】《垃圾回收的标记清除算法详解》 博文
【4】《javascript中的内存管理和垃圾回收》小火柴的蓝色理想
【5】《内存泄漏》搜狗百科
【6】《内存机制相关面试题》
【n】其余多个,,,若有雷同,那就是了。。。