JavaScript中的内存释放
zwba0830
8年前
<h2>一、如何查找上级作用域</h2> <p>在JavaScript中的预解析 ,有讲到作用域链的概念,本次在正式讲JavaScript中的内存释放之前,先看一个问题: <strong>如何查找当前作用域的上级作用域</strong> 。</p> <pre> <code class="language-javascript">var num = 20; function fn() { var num = 200; return function () { console.log(num); }; } var f = fn(); f(); // 输出 200</code></pre> <p>以上代码fn中返回了一个函数,用f去接收这个返回的函数,然后再执行f(),最后输出的是200,刚接触的同学可能会有疑问,为什么在全局作用域下执行的f(),为什么输出的num不是全局作用域中的20,而是fn函数的私有作用域中的200!</p> <p>上级作用域查找规则:看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁,和函数哪里执行没有任何的关系。</p> <p>以上的代码在全局的变量和函数进行预解析之后,执行fn函数,fn函数又进行预解析形成自己的私有作用域,然后执行fn函数中的代码,最后返回一个函数,该函数被f接收。当f执行的时候,有一行代码输出num: console.log(num) ,根据作用域搜索规则,首先在自己的作用域中找,没有找到,然后再到上级的作用域中查找,根据作用域查找的规则, <strong>只看当前函数在哪个作用域下定义的</strong> ,所以f函数的上级作用域是fn。</p> <p><img src="https://simg.open-open.com/show/d417228f6b2ba49d4e0b5a417500e7a7.png"></p> <p>如何查找上级作用域.png</p> <p>有了以上的基础之后,再看:</p> <pre> <code class="language-javascript">var num = 20; function fn() { var num = 200; return function () { console.log(num); }; } var f = fn(); f(); // 输出 200 ~function () { var num = 2000; f(); // 输出什么呢? }();</code></pre> <p>加上了一个自执行函数,我们知道自执行函数是有自己的作用域的,但是此时f函数执行,依然输出200。 要时刻上级作用域的查找规则: <strong>只看当前函数在哪个作用域下定义的</strong> 。</p> <p>这时候很多同学可能会疑惑了,我们不是讲解JavaScript的内存释放的嘛?怎么还讲起了作用域的内容,稍安勿躁...在JavaScript的内存释放中要用到这些知识呢!</p> <h2>二、堆内存的释放</h2> <p>对象数据类型或者函数类型在定义的时候,首先都会开辟一个堆内存,堆内存有一个引用地址,如果外面有引用这个地址,我们就说这个内存被占用了,就不能销毁了。</p> <pre> <code class="language-javascript">var obj1 = {name:"iceamn"}; var obj2 = obj1;</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/5e9841dc5703846eeb559c67f7d55f7f.png"></p> <p>堆内存.png</p> <p>如果想要让堆内存释放(销毁),只需要把所有引用它的变量赋值为null即可,如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把它销毁。也就是说,在上面的那种感觉情况下,只有把obj1和同obj2都置为null之后,0xff11这块对堆内存才会被释放,只要还有变量引用0xff11这块内存,它就不会释放。</p> <h2>三、栈内存的释放</h2> <p>3.1、全局作用域</p> <p>在全局作用域下,只有当页面关闭的时候,全局作用域才会被销毁。</p> <h3>3.2、私有作用域</h3> <p>一般情况下,函数执行会形成一个新的私有作用域(在ES6之前只有函数执行才会产生私有作用域),当私有作用域中的代码执行完成后,当前作用域都会主动的进行释放和销毁。</p> <p>不过依然有特殊的情况存在:当前私有作用域中的部分内容被作用域以外的东西占用了,那么当前作用域就不能销毁了。</p> <p>3.2.1、 函数返回来一个引用数据类型的值(数组、函数...),并且该引用类型的值在函数的外面被一个其他变量接收了,这种情况下形成的私有作用域都不会销毁。</p> <p>注意两个条件:</p> <p>(1)函数返回引用数据类型的值;</p> <p>(2)该引用类型的值在函数外面被一个其他变量接收了;</p> <pre> <code class="language-javascript">function fn() { var num = 100; return function () { num ++; console.log(num); } } var f = fn(); // fn执行形成的作用域就不能再销毁了</code></pre> <p>注意:即使fn返回的函数中什么代码都没有,没有使用到fn私有作用域中的任何变量和函数,在以上情况下,fn的私有作用域也不会被销毁,即:</p> <pre> <code class="language-javascript">function fn() { var num = 100; return function () { } } var f = fn();</code></pre> <p>3.2.2、 在一个私有作用域中,给DOM元素绑定方法,私有作用域不能被销毁</p> <pre> <code class="language-javascript">var btn = document.getElementById('btn1'); ~function () { btn.onclick = function () { } }();</code></pre> <p>在自执行函数中形成了一个私有的作用域,在这个私有作用域中为页面上的一个button元素绑定了点击事件,所以这个私有作用域也不能被销毁。</p> <p><img src="https://simg.open-open.com/show/f52a227688f4a2351a558c89e46265c5.png"></p> <p>自执行函数的情况.png</p> <p>3.2.3、 “不立即销毁”</p> <pre> <code class="language-javascript">function fn() { var num = 100; return function () { } } fn()(); // 首先执行fn,返回一个小函数对应的内存地址,然后紧接着让返回的小函数再执行</code></pre> <p>以上代码就是“不立即销毁”的情况,fn返回的函数没有被其他的任何变量占用,但是还需要执行一次,所以暂时不能销毁,但返回的值执行完成后,浏览器会在空闲的时候把它销毁了。</p> <p>还记得一开始介绍的上级作用域吗,我们再对那张图进行分析:</p> <p><img src="https://simg.open-open.com/show/05b7ef251ef646b299f0b138392feb0f.png"></p> <p>上级作用域的情况.png</p> <p>只要某作用域还有被引用,那么该作用域就不能被销毁,一旦没有任何变量引用了,该私有作用域就会被销毁了。</p> <h2>四、练习题</h2> <ul> <li> <p>第一题</p> <pre> <code class="language-javascript">function fn() { var i = 10; return function (n) { console.log(n + (++i)); }; } var f = fn(); f(10); // 21 f(20); // 32 fn()(10); // 21 fn()(20); // 31</code></pre> </li> <li> <p>第二题</p> <pre> <code class="language-javascript">function fn(i) { return function (n) { console.log(n + i++); } } var f = fn(13); f(12);//->25 f(14);//->28 fn(15)(12);//->27 fn(16)(13);//->29</code></pre> </li> </ul> <p> </p> <p>来自:http://www.jianshu.com/p/3b7946c4b118</p> <p> </p>