(关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导)前端
本周正式开始前端进阶的第一期,本周的主题是调用堆栈,今天是第5天。node
本计划一共28期,每期重点攻克一个面试重难点,若是你还不了解本进阶计划,点击查看前端进阶的破冰之旅webpack
若是以为本系列不错,欢迎转发,您的支持就是我坚持的最大动力。git
4类 JavaScript 内存泄漏及如何避免 ,因为微信不能访问外链,点击阅读原文就能够啦。es6
上篇文章介绍了垃圾回收机制,可是都是些概念,今日份文章(译文)有代码有讲解,详解介绍了经常使用内存泄漏并说明了如何避免,对于提高我的知识深度很是有帮助。github
上篇文章详细介绍了内存回收和内存泄漏,今天咱们继续这个篇幅,不太重点是内存泄漏可能发生的缘由。没看过上篇的点击【进阶1-4期】JavaScript深刻之带你走进内存机制web
经常使用垃圾回收算法叫作标记清除 (Mark-and-sweep) ,算法由如下几步组成:面试
现代的垃圾回收器改良了算法,可是本质是相同的:可达内存被标记,其他的被看成垃圾回收。算法
划重点 这是个考点跨域
未定义的变量会在全局对象建立一个新变量,以下。
function foo(arg) { bar = "this is a hidden global variable"; }
函数 foo
内部忘记使用 var
,实际上JS会把bar挂载到全局对象上,意外建立一个全局变量。
function foo(arg) { window.bar = "this is an explicit global variable"; }
另外一个意外的全局变量可能由 this
建立。
function foo() { this.variable = "potential accidental global"; } // Foo 调用本身,this 指向了全局对象(window) // 而不是 undefined foo();
解决方法:
在 JavaScript 文件头部加上 'use strict'
,使用严格模式避免意外的全局变量,此时上例中的this指向undefined
。若是必须使用全局变量存储大量数据时,确保用完之后把它设置为 null 或者从新定义。
计时器setInterval
代码很常见
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);
上面的例子代表,在节点node或者数据再也不须要时,定时器依旧指向这些数据。因此哪怕当node节点被移除后,interval 仍旧存活而且垃圾回收器没办法回收,它的依赖也没办法被回收,除非终止定时器。
var element = document.getElementById('button'); function onClick(event) { element.innerHTML = 'text'; } element.addEventListener('click', onClick);
对于上面观察者的例子,一旦它们再也不须要(或者关联的对象变成不可达),明确地移除它们很是重要。老的 IE 6 是没法处理循环引用的。由于老版本的 IE 是没法检测 DOM 节点与 JavaScript 代码之间的循环引用,会致使内存泄漏。
可是,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法(标记清除),已经能够正确检测和处理循环引用了。即回收节点内存时,没必要非要调用 removeEventListener
了。
若是把DOM 存成字典(JSON 键值对)或者数组,此时,一样的 DOM 元素存在两个引用:一个在 DOM 树中,另外一个在字典中。那么未来须要把两个引用都清除。
var elements = { button: document.getElementById('button'), image: document.getElementById('image'), text: document.getElementById('text') }; function doStuff() { image.src = 'http://some.url/image'; button.click(); console.log(text.innerHTML); // 更多逻辑 } function removeButton() { // 按钮是 body 的后代元素 document.body.removeChild(document.getElementById('button')); // 此时,仍旧存在一个全局的 #button 的引用 // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。 }
若是代码中保存了表格某一个 <td>
的引用。未来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <td>
之外的其它节点。实际状况并不是如此:此 <td>
是表格的子节点,子元素与父元素是引用关系。因为代码保留了 <td>
的引用,致使整个表格仍待在内存中。因此保存 DOM 元素引用的时候,要当心谨慎。
闭包的关键是匿名函数能够访问父级做用域的变量。
var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) console.log("hi"); }; theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log(someMessage); } }; }; setInterval(replaceThing, 1000);
每次调用 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 基本类型 之一。
var data; console.log(data === undefined); //true
对于还没有声明过的变量,只能执行一项操做,即便用typeof操做符检测其数据类型,使用其余的操做都会报错。
//data变量未定义 console.log(typeof data); // "undefined" console.log(data === undefined); //报错
值 null
特指对象的值未设置,它是 JavaScript 基本类型 之一。
值 null
是一个字面量,它不像undefined
是全局对象的一个属性。null
是表示缺乏的标识,指示变量未指向任何对象。
// foo不存在,它历来没有被定义过或者是初始化过: foo; "ReferenceError: foo is not defined" // foo如今已是知存在的,可是它没有类型或者是值: var foo = null; console.log(foo); // null
问题二:
ES6语法中的 const 声明一个只读的常量,那为何下面能够修改const的值?
const foo = {}; // 为 foo 添加一个属性,能够成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另外一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
解答:
const
实际上保证的,并非变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,所以等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即老是指向另外一个固定的地址),至于它指向的数据结构是否是可变的,就彻底不能控制了。所以,将一个对象声明为常量必须很是当心。
<script> console.log(fun) console.log(person) </script> <script> console.log(person) console.log(fun) var person = "Eric"; console.log(person) function fun() { console.log(person) var person = "Tom"; console.log(person) } fun() console.log(person) </script>
上面代码的执行结果是什么?先本身分析,而后再到浏览器中执行。
4类 JavaScript 内存泄漏及如何避免
每周面试重难点计划以下,若有修改会通知你们。每周一期,为期半年,准备明年跳槽的小伙伴们能够把本公众号[置顶]()了。
本人Github连接以下,欢迎各位Star
http://github.com/yygmind/blog
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
若是你想加群讨论每期面试知识点,公众号回复[加群]便可