+ 2.知识剖析
+
+
+
不管什么程序语言,内存生命周期基本是一致的:
+
1:分配你所需要的内存
+
2:使用分配到的内存(读、写)
+
3:不需要时将其释放\归还
+
所有语言第二部分都是明确的。第一和第三部分在底层语言中是明确的,但在像JavaScript这些高级语言中,大部分都是隐含的。
+
为了不让程序员费心分配内存,JavaScript 在定义变量时就完成了内存分配。
+
像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。
+
+
+
+
+
JS的回收机制
+
JavaScript垃圾回收的机制:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统会按照固定的时间间隔,周期性的执行。(类似节流函数)
+ 到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。
+
+
+
+
+
+
引用计数
+
引用计数的含义是跟踪记录每个值被引用的次数。
+ 当声明了一个变量并将一个引用类型值(function object array)赋给该变量时,则这个值的引用次数就是1。
+ 如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。
+
+
+
+
+
例子:
+
function test(){
+
var a={};// 声明变量为引用类型 a的引用次数为0
+
var b=a;//a的引用次数加1,为1
+
var c=a;//a的引用次数加1,为2
+
var b={};//b引用被覆盖,a的引用次数减1,为1
+
}
+
+
+
+
+
引用计数有个缺陷,无法处理循环引用的事例,如下
+
function f(){
+
var o = {};
+
var o2 = {};
+
o.a = o2; // o 引用 o2
+
o2.a = o; // o2 引用 o
+
return "azerty";}
+
f();
+
在上面的例子中,两个对象被创建,并互相引用,形成了一个循环。
+ 它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。
+ 然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。造成了内存泄漏。
+
+
+
+
+
标记清除
+
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”
+
js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。
+ 从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。
+ 而当变量离开环境时,则将其标记为“离开环境”。
+ function test() {
+
var a=10;//被标记,进入环境
+
var b=20;//被标记,进入环境
+ }
+
test();//执行完毕之后a、b又被标记离开环境,被回收
+
+
+
+ 4.解决方案
+
+
+
+
+ ①,意外的全局变量引起的内存泄露(全局变量只有关闭/刷新浏览器才会被释放或回收)
+
function leak(){
+
leak="xxx";//没声明导致leak成为一个全局变量,不会被回收
+
}
+
console.log(leak) // xxx
+
+
+
+ ②意外的this创建了全局变量
+
function foo() {
+
this.variable = "潜在的全局变量";
+
}
+
// 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'
+
foo();
+
+
+
+
+
解决方法:
+
在JavaScript文件中添加'use strict',开启严格模式,可以有效地避免上述问题
+
function foo() {
+
"use strict" // 在foo函数作用域内开启严格模式
+
bar = "未声明的全局变量";
+
// 报错:因为bar还没有被声明
+
}
+
+
+
+
+ 如果需要在一个函数中使用全局变量,可以像如下代码所示,在window上明确声明:
+
function foo() {
+
window.bar = "这是一个全局变量";
+
}
+
关于全局变量,优化可以在使用完之后,给全局变量赋值为null,进行内存回收,也是一个优化方法。
+
+
+
+
+
常见内存泄漏问题:
+
console.log:向web开发控制台打印一条消息,常用来在开发时调试分析。有时在开发时,需要打印一些对象信息,但发布时却忘记去掉console.log语句,这可能造成内存泄露。
+
在传递给console.log的对象是不能被垃圾回收 ♻️,因为在代码运行之后需要在开发工具能查看对象信息。所以最好不要在生产环境中console.log任何对象。
+
用完了在整合项目的时候记得注释掉或者删除。
+
+
+
+
+ ②,闭包引起的内存泄露
+
闭包就是能够读取其他函数内部变量的函数,相当于函数内部函数调用父级变量,闭包通常用来引出函数内变量,而这种行为会导致闭包一直占用内存,为了优化,或者说避免内存泄漏,在退出函数前需要删除用不到的局部变量
+
function assignHandler ( ) {
+
var element = document .getElementById( "someElement" );
+
element.onclick = function ( ) {
+
alert(element.id);
+
};
+
+
匿名函数调用函数外的element,导致element至少有一次引用,不会被清除,造成内存泄漏。
+
+
+
+
+
+ 解决办法:
+
function Handler ( ) {
+
var element = document .getElementById( "someElement" );
+
var id = element.id // 变量相当于记录了element.id
+
element.onclick = function ( ) {
+
alert(id); // 此时
+
}
+
element = null //回收,
+
};
+
+
+
+
+ dom泄漏
+
为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。
+
但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露。
+
同样把局部变量 = null进行释放。
+
+
+
+
+
+ timer未clear造成的泄漏
+
如果在不需要setInterval()时,没有通过clearInterval()方法移除,那么setInterval()会不停地调用函数,
+ 直到调用clearInterval()或窗口关闭。如果链式setTimeout()调用模式没有给出终止逻辑,
+ 也会一直运行下去。因此再不需要重复定时器时,确保对定时器进行清除,避免占用系统资源。
+
+
+
+
+