Open yygmind opened 5 years ago
多谢 平时写代码对这个内存泄露的点考虑的少了点 想着在浏览器停留的时间较短 应该一直养成这种思维习惯
如果代码中保存了表格某一个
的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 以外的其它节点。实际情况并非如此:此 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 的引用,导致整个表格仍待在内存中。所以保存 DOM 元素引用的时候,要小心谨慎。 这个是表格的特性还是说是所有DOM的特性?
orGancode commented 5 years ago<script> console.log(fun) // undefined console.log(person) // undefined </script> <script> console.log(person) // undefined console.log(fun) // fun(){} var person = "Eric"; console.log(person) // Eric function fun() { console.log(person) // undefined var person = "Tom"; console.log(person) // Tom } fun() console.log(person) // Eric </script>
Randysheng commented 5 years ago第一个 script :
<script> console.log(fun) console.log(person) </script>
当前全局作用域中并没有定义 fun 和 person,那么执行
console.log(fun)
会导致报错提示 fun 没有定义,并且会阻断代码继续执行,也就不会执行console.log(person)
。第二个 script:
<script> console.log(person) // (1) console.log(fun) // (2) var person = "Eric"; console.log(person) // (3) function fun() { console.log(person) // (4) var person = "Tom"; console.log(person) // (5) } fun() console.log(person) // (6) </script>
虽然变量 person 和函数 fun 是在下方定义的,但是会发生变量提升和函数提升,因此: (1) 处打印 undefined (2) 处打印 function fun() { ... }
随后变量 person 被赋值为”Eric“ (3) 处打印 Eric
由于在函数 fun 中,重新定义了一个 person 变量 (4) 处打印 undefined (5) 处打印 Tom
全局作用域中存在一个变量 person (6) 处打印 Eric
Nina0408 commented 5 years ago闭包的内存泄露没有看懂,能给更具体点的分析吗?谢谢啦!
kukudeshiyi commented 5 years ago最近看过一个题,如何定位(检查)内存泄漏?想了想但不确定怎么回答,在此求解
YeziZhao commented 5 years ago我分析一下闭包的哪一个,有问题请指出来,谢谢
// 初始化定义theThing var theThing = null; var replaceThing = function () { // originalThing指向theThing, 当第二次执行这句话时,内部变量会指向前一次的 theThing var originalThing = theThing; var unused = function () { // 由于 originalThing 在 unused 中进行了引用,unused不被释放,相当于前一次的theThing地址指向的内容没有被释放 if (originalThing) console.log("hi"); };
theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log(someMessage); } }; };
// setInterval 每隔 1000 秒执行 一次replaceThing, setInterval(replaceThing, 1000);
假如每一轮的originalThing最后设置为null, 那么 unused 没有使用有效的变量会被释放。每一轮的theThing重新指向了一个新的地址,那么前一轮的内容也会被释放(因为originalThing没有再引用前一轮的值)
5201314999 commented 5 years agonull 和 undefined 的说明没什么说服力
1045598742 commented 5 years ago垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方 式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记 的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器 完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 到 2008 年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的 垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。
和js高级程序上面说的不一样啊,不知道谁说的对了.... 书上说的是把引用的标记去掉,删除的是标记着的变量(不知道是不是我理解错了,求大佬帮解释一下)
YorrickBao commented 5 years ago给一个全局变量赋值为undefined,相当于将这个对象的值清空,但是这个对象依旧存在,如果是给对象的属性赋值 为undefined,说明这个值为空值
不太好理解,可以给出它们俩区别的代码示例吗?
Ybbbb commented 2 years ago所以如果给一个全局变量赋值为undefined,它会被gc回收吗
yanpei2016 commented 2 years ago你的来信我一收到 谢谢哈 ~~~~
kingforever commented 2 years ago存收到,谢谢!
本期的主题是调用堆栈,本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,文末点击查看全部文章。
如果觉得本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。
上篇文章详细介绍了内存回收和内存泄漏,今天我们继续这个篇幅,不过重点是内存泄漏可能发生的原因。
垃圾回收算法
常用垃圾回收算法叫做标记清除 (Mark-and-sweep) ,算法由以下几步组成:
1、垃圾回收器创建了一个“roots”列表。roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
2、所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。
3、所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。
现代的垃圾回收器改良了算法,但是本质是相同的:可达内存被标记,其余的被当作垃圾回收。
四种常见的JS内存泄漏
划重点 这是个考点
1、意外的全局变量
未定义的变量会在全局对象创建一个新变量,如下。
函数
foo
内部忘记使用var
,实际上JS会把bar挂载到全局对象上,意外创建一个全局变量。另一个意外的全局变量可能由
this
创建。解决方法:
在 JavaScript 文件头部加上
'use strict'
,使用严格模式避免意外的全局变量,此时上例中的this指向undefined
。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。2、被遗忘的计时器或回调函数
计时器
setInterval
代码很常见上面的例子表明,在节点node或者数据不再需要时,定时器依旧指向这些数据。所以哪怕当node节点被移除后,interval 仍旧存活并且垃圾回收器没办法回收,它的依赖也没办法被回收,除非终止定时器。
对于上面观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。因为老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。
但是,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法(标记清除),已经可以正确检测和处理循环引用了。即回收节点内存时,不必非要调用
removeEventListener
了。3、脱离 DOM 的引用
如果把DOM 存成字典(JSON 键值对)或者数组,此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。那么将来需要把两个引用都清除。
如果代码中保存了表格某一个
<td>
的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的<td>
以外的其它节点。实际情况并非如此:此<td>
是表格的子节点,子元素与父元素是引用关系。由于代码保留了<td>
的引用,导致整个表格仍待在内存中。所以保存 DOM 元素引用的时候,要小心谨慎。4、闭包
闭包的关键是匿名函数可以访问父级作用域的变量。
每次调用
replaceThing
,theThing
得到一个包含一个大数组和一个新闭包(someMethod
)的新对象。同时,变量unused
是一个引用originalThing
的闭包(先前的replaceThing
又调用了theThing
)。someMethod
可以通过theThing
使用,someMethod
与unused
分享闭包作用域,尽管unused
从未使用,它引用的originalThing
迫使它保留在内存中(防止被回收)。解决方法:
在
replaceThing
的最后添加originalThing = null
。PS:今晚弄到很晚,由于时间问题,就不再详细介绍Chrome 内存剖析工具,有兴趣的大家去原文查看。
周末汇总将在周日早上发送,周六会发送其他类型的文章,敬请期待。
昨日思考题解答
问题一:
从内存来看 null 和 undefined 本质的区别是什么?
解答:
给一个全局变量赋值为null,相当于将这个变量的指针对象以及值清空,如果是给对象的属性 赋值为null,或者局部变量赋值为null,相当于给这个属性分配了一块空的内存,然后值为null, JS会回收全局变量为null的对象。
给一个全局变量赋值为undefined,相当于将这个对象的值清空,但是这个对象依旧存在,如果是给对象的属性赋值 为undefined,说明这个值为空值
扩展下:
声明了一个变量,但未对其初始化时,这个变量的值就是undefined,它是 JavaScript 基本类型 之一。
对于尚未声明过的变量,只能执行一项操作,即使用typeof操作符检测其数据类型,使用其他的操作都会报错。
值
null
特指对象的值未设置,它是 JavaScript 基本类型 之一。值
null
是一个字面量,它不像undefined
是全局对象的一个属性。null
是表示缺少的标识,指示变量未指向任何对象。问题二:
ES6语法中的 const 声明一个只读的常量,那为什么下面可以修改const的值?
解答:
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。今日思考题
上面代码的执行结果是什么?先自己分析,然后再到浏览器中执行。
参考
进阶系列目录
交流
进阶系列文章汇总:https://github.com/yygmind/blog,内有优质前端资料,欢迎领取,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!