接着上篇文章我们继续探讨关于预解释的问题:当预解释的函数问题遇见诸如内存释放结合的时候,我们需要格外小心,我们都知道JavaScript属于弱类型语言,起初只是作为浏览器的脚本语言,现今js的用途变得越来越广泛,但作为一种单线程语言,性能优化则变得尤为重要,什么异步回调,浏览器自身的垃圾回收机制等各种行为都是为了优化性能。扯远了,闲话少说,接下来步入正题。为了更好,更快的地运行代码,我们需要了解内存释放机制:
- 内存包含:堆内存和栈内存
- 堆内存:用来存放数据的;
+ 对象数据类型的
+ 存的是键值对 key=value;
+ 函数数据类型的
+ 代码字符串
- 堆内存的释放:
var a=[1,2,3,4]
释放:a=null;
- 栈内存:本身提供了一个供JS代码执行的环境
+ 包含:全局作用域 和 私有作用域
- 全局作用域的形成和销毁:
+ 形成:当一个页面被浏览器加载完成的时候,全局作用域就形成了;
+ 销毁:1)关闭页面 2)关闭浏览器
- 私有作用域的形成和销毁:
+ 形成:当函数被调用的时候,会形成私有作用域
+ 销毁:一般情况下,但函数执行完成的时候,默认就被销毁了;但是两种情况下不销毁:
+ 不销毁:当函数体内的东西被外面的变量或者其他占用的话,就不销毁;
+ 不立即销毁:当函数执行完成的时候,会返回一个函数,被返回的函数还需要再执行一次;只有所有的调用都完成的时候,这个函数才能销毁;
简言之:1.就是全局变量类似于 var a=function fn(){}这种情况下fn 被函数占据,若果变量不重新赋值(或赋值为null),函数就会始终存在于内存中不释放;
2.当function f1(){return function f2(){}}这类一种函数存在于另一个函数作用域中,当调用f2时,f2的执行依赖于f1的执行,此时f1不立即销毁,等f2执行完成后会销毁。
弄明白原理后,我们来根据实例具体阐述:
例1:
var i=3;
function fn(){
i*=2;
return function(n){
console.log(n*(++i))
}
}
var f=fn();
f(3)
fn()(3);
f(4);
fn()(3);
解析:1.解题思路:做此类题时,我的做法是先不考虑什么预解释,先通篇看一下在全局变量中是否有函数被全局变量占据,此题var f=fn();fn函数被全局变量f占据,所以类似于执行f()执行时,函数fn永远不会释放,里面i的值依然依次利用。但fn()(),恰好相反,执行依次释放依次,每次调用彼此之间互不影响。此上我觉得应该才是考点。
2.接下来正式解题,先对变量和函数进行预解释,var i;function fn(){ i*=2;returnfunction(n){
console.log(n*(++i))
}
};var f;
3.代码从上到下执行:i=3;f=fn()=function(n){
console.log(n*(++i))
} *fn()执行后的结果就是它的返回值
4.开始真正的做题:(谨记此题全局变量提供一切私有变量的值)
f(3):
就相当于function(3){
console.log(3*(++i))
}
解析:当前作用域中没有i;所以上级作用域去找找到i*=2;依然没有确切的值继续沿着作用域链找i=3;找到后再沿着反方向计算回去,i=6;所以最后console的结果为3*7=21;此处又有一个知识点i++和++i的区别,i++是先执行算,如果此题换成i++,就运算完了3*6=18;然后在i++=7;而++i正好相反,它是先运算后执行,先自身++为7再运算*3结果为21,此处我们应该可以想起刚才解析的考点,关键点说三遍,函数f不释放!不释放!!不释放!!!此时全局变量i=7
fn()(3):
解析:这也是考察的基本原理,函数fn执行后得到的函数再执行得到结果,fn()执行i*=2,因为全局变量i值为7,,所以i=2*7=14; fn()(3)此时console的值为3*++i=3*(14+1)=3*15=45;所以值为45;因为没有变量的占据,fn必须得释放;此时全局变量i的值为15;
f(4):
从这一步开始才算真正进入差异化:因为f()再执行时,fn被变量f占据,并没有释放,所以不会再一次执行,所以直接套用i值,此时直接执行作为返回值的那个函数,得出结果为4*++i=4*(15+1)=4*16=64;此时全局变量i的值为16;
fn()(3):
这一步我们注意到,没有了全局变量占据着的函数,所以第二步函数已经释放了,来吧从新执行吧,fn() i*=2=16*2=32;fn()(3) 3*(32+1)=3*33=99 ,此时全局变量i的值为33;
我相信大家对预解释都很熟悉,从下题开始浅显的进行预解释,旨在对题的关键点做更细微的诠释,
上面那道题,比较简单,那么如果你认为它完了你就错了!来让我们接着变,接着引申:
此题做稍微的变化:
例2:引申
var i=3;
function fn(){
i*=2;
var i=3;
return function(n){
console.log(n*(++i))
}
}
var f=fn();
f(3)
fn()(3);
f(4);
fn()(3);
解析:这道题的思路与上题类似,有一个点需要注意就是函数内部如果有私有变量那么他就不会去别的父级作用域上取值,所以此题中i的值是以私有变量i值为基准:
故解题步骤可以相较于例1,主要变的是私有变量i,不关全局变量的事,所以结果为
12,12,20,12;此题还有一个要点就是全局变量不会主观上释放,但私有变量随着函数执行完毕后随之释放,所以每次除了被变量占据的函数由于不释放私有变量的值依然在累加沿用,其他类似于此题中fn()(3),中的i值都会随着fn释放重新取值;
想一下此类题还能怎么改?相信大家都会想起来,那就是函数传参数,就类似于隐含的私有变量而已,有兴趣的可以一试;
本人做题总结出此类题的小技巧:
做预解释或理解预解释时,1.先看全局是否有var某个变量(假设为f)占据着某个函数,若有则在进行f()时,f不释放;2.若某变量为自执行函数的返回值时,若有则返回,没有则返回undefined;3.在函数执行时看自己作用域内是否有私有变量,若没有则需要注意对全局变量的影响,同时更加需要注意的是函数的声明优先于变量的声明;
下篇继续来扯扯与面向对象原型相关的预解释,下章依然酸爽。。。。