注:本文中预解释就是 函数在执行时进行的例如变量提升
一. 在JavaScript中的函数理解中预解释是一个比较难懂的话题。原理虽然简单,寥寥数言,但其内涵却有深意,精髓难懂。如何在轻松活跃的头脑中将它学会,现在针对我在学习中的一点小窍门给大家分享一下,希望能给大家一些帮助:
万事需遵循“原理”——“预解释”无节操和“this”指向:(可先看例题解析然后结合原理进行学习)
如果函数传参数则先于以下执行,就相当于在函数私有作用域下var了一个变量;根据作用域原理,私有作用域的权重永远大于父级作用域,当我私有作用域中有某个变量时,而我正好需要他时,私有作用域变量的值就是我所需要的;当私有作用域中没有时,而我有恰好需要它时,我就会沿着作用域链一级级的往上找。此时可得出子级可以用父级的变量,但父级不能访问子级的作用域,打个简单的比喻可能不太贴切,子孙能继承祖宗的财产,但祖宗不能继承子孙的财产,因为祖宗都没了,还怎么继承。
只对等号左边带var的,声明但不定义
自执行函数不会进行预解释,只有,执行到他的时候,声明+定义+调用同步完成
已经声明过的不会进行重复声明,但会重新赋值;
return下面的语句虽然不会执行,但会进行预解释;
函数的声明早于变量的声明
在条件判断语句中,无论条件是否成立,都会进行预解释;
不要在条件判断语句中写函数的定义阶段;否则,各大浏览器对其的兼容性不同
this指向:当一个元素身上的事件被触发的时候,会执行一个函数,函数中的this指向当前这个元素;
自执行函数中的this,永远都是window;
回调函数中的this,一般都是window; setInterval(函数名,1000) ary.sort(function(){})
当函数被调用的时候,看前面是否有".","."前面是谁,this就是谁;
原理阐述篇————实例小窍门篇:
例1:
(function f(){
function f(){ return 1; }
alert (f());
function f(){ return 2; }
})();
解析:此函数为自执行函数,观察函数,注意类似于function fn(){}这样的才是声明函数,像var f=function(){},b=function(){}这样的都不属于声明函数,前者属于变量的声明,
只有像声明函数那样的在函数才会进行预解释,此题中两个为需要声明的函数为同名函数,所以根据‘已经声明过的不会进行重复声明,但会重新赋值’原理得到函数预解释为 function f(){ return 2; }
此时,当函数执行时
alert (f())的结果为2;
例2:
var a=12;
function show(){
alert(a);
a=15;
}
show();
alert(a);
解析:此题为对作用域链的考察,先进行预解释,特别提示变量只有声明,函数既有声明还有定义,预解释了之后代码执行时就会自动跳过不执行,但var a=12,这相当于给变量重新赋值,因为任何变量进行预解释的初始值为
undefined;本题先对变量和函数进行预解释:var a=undefined;
function show(){
alert(a);
a=15;
}
预解释完毕后,代码从上往下的执行,1. a=12; 2. function show(){alert(a);a=15}已经经过预解释不会执行; 3. show()进行函数的调用,alert(a).当前作用域下没
有a,就去父级作用域,a=12; 4.接着执行a=15,任何在函数私有作用域下'变量'前面不带var的本身不属于当前作用域域,而是对全局作用域中全局变量的重新赋值,所
以此时相当于把全局变量中的a直接赋值为15; 5.最后在全局作用域下alert(a),只能访问自己的变量。所以a=15;
此题答案为 12,15;
此题引申:
var a=12;
function show(){
alert(a);
var a=15;
}
show();
alert(a);
此时输出的结果为undefined,12,你猜对了吗?切记私有作用域中的变量是以var为基准的,当然如果传参数例外,因为传参就相当于最先var,此题在show函数执行时,因为
里面有var,所以肯定有私有变量所以var a,接着alert(a),大家应该没有忘记任何变量的预解释初始值为undefined,接着执行var a=15,此时相当于重新赋值,这里不用有疑问,因为代码
是从上往下执行,接着继续执行外面全局变量中的alert(a)此时值为12,因为函数私有作用域中并没有改变全局变量的值。
再引申:
var a=12;
function show(a){
alert(a);
var a=15;
}
show(a);
alert(a);
事已至此,咱们再引申一下:
var a=12;
function show(a){
alert(a);
a=15;
}
show(a);
alert(a);
此题结果应该为:12,15,此题主要关键点为全局变量的从新赋值:
那么大家肯定就更能做出这道题了:
alert(a);
var a = 12;
function show(a) {
alert(a);
var a = 15;
alert(a);
}
show(a);
alert(a);
此题结果为undefined,12,15,12
例2:接下来我们来讨论一下变量和函数的一些关系:
function a() {
var b=10;
alert(b);
};
var a;
a();
解析:此题中变量和函数为同名,根据原理知识我们可以知道,函数的声明要优先于变量的声明,所以当同名时以函数优先,所以不难的出此题的答案为10;别急,你以为这么简单就完了,来开始做个小修改
引申一下:
function a() {
var b=10;
alert(b);
};
var a=6;
a();
解析:大家猜猜这道题的结果,如果你对上面的原理理解的不错的话,那么你应该很容易得出此题会报错,a is not defined; 确实函数占用了名称,当接下来执行var a=6,时,大家要注意了,只要变量
有等号就相当于重新赋值,但大家疑问来了,为啥会报错呢,请注意a的名字已经被函数占用,当你进行var a=6,时此时你又把a赋值给6,此时函数本身就犹如孤魂野鬼,没有名字,当最后调用啊a()我去哪找函数a,所以必须的必肯定会报错!!!
例3:
if("a" in window){
var a=15;
}
function fn() {
alert(a)
}
fn()
解析:此题考查的知识点为在条件判断语句中,无论条件是否成立,都会进行预解释,在此题中,大家也应该记住类似于“a” in window 都是正确的,所以起先会进行预解释 var a, 因为条件成立,进入
条件语句中会进行重新赋值,此时a=15;在函数中alert(a),向上级作用域寻找a,结果为15;
引申:
if(!"a" in window){
var a=15;
}
function fn() {
alert(a)
}
fn()
解析:加个“!”,条件肯定为false,不进入条件,但会进行预解释var a,此时a 的值为undefined,
接下来将他们综合起来先来个简单的综合:
var name='haha';
var age=500;
name=(function(name,age){
arguments[0]='xixi';
age=age||this.age;
console.log(name,age);
})(name);
console.log(name,age)
解析:看到此题,来吧先进行预解释,1.var name;var age;此时有个注意点,name=...;等号后面的函数不会进行预解释。2.代码从上到下执行,name='haha';age=500;3.接下来是个关键点,name等于一个自执行函数,先看看它有没有返回值,一个函数执行后若没有返回值则结果都等于undefined,所以此时全局name=undefined,此时自执行函数执行时,函数经过传参,就相当于提前var,所以有私有变量,arguments[0]='xixi';此时就相当于将函数私有作用域中name=xixi;age=age||this.age,自执行函数的this指向为window,"||"为或的意思,有一项成立就行,那么age=500.第一次console结果应该为xixi,500 4.全局的console我们刚才分析了name富裕没有返回值的自执行函数时为undefined,所以结果为undefined,500;
二. 当预解释的函数问题遇见诸如内存释放结合的时候,我们需要格外小心,我们都知道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执行完成后会销毁。
弄明白原理后,我们来根据实例具体阐述:
例4:
解析: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的结果为37=21;此处又有一个知识点i++和++i的区别,i++是先执行算,如果此题换成i++,就运算完了36=18;然后在i++=7;而++i正好相反,它是先运算后执行,先自身++为7再运算3结果为21,此处我们应该可以想起刚才解析的考点,关键点说三遍,函数f不释放!不释放!!不释放!!!此时全局变量i=7
fn()(3):
解析:这也是考察的基本原理,函数fn执行后得到的函数再执行得到结果,fn()执行i=2,因为全局变量i值为7,,所以i=27=14; fn()(3)此时console的值为3++i=3(14+1)=315=45;所以值为45;因为没有变量的占据,fn必须得释放;此时全局变量i的值为15;
f(4):
从这一步开始才算真正进入差异化:因为f()再执行时,fn被变量f占据,并没有释放,所以不会再一次执行,所以直接套用i值,此时直接执行作为返回值的那个函数,得出结果为4++i=4(15+1)=416=64;此时全局变量i的值为16;
fn()(3):
这一步我们注意到,没有了全局变量占据着的函数,所以第二步函数已经释放了,来吧从新执行吧,fn() i=2=162=32;fn()(3) 3(32+1)=3*33=99 ,此时全局变量i的值为33;
我相信大家对预解释都很熟悉,从下题开始浅显的进行预解释,旨在对题的关键点做更细微的诠释,
上面那道题,比较简单,那么如果你认为它完了你就错了!来让我们接着变,接着引申:
此题做稍微的变化:
例4:
解析:这道题的思路与上题类似,有一个点需要注意就是函数内部如果有私有变量那么他就不会去别的父级作用域上取值,所以此题中i的值是以私有变量i值为基准:
故解题步骤可以相较于例1,主要变的是私有变量i,不关全局变量的事,所以结果为
12,12,20,12;此题还有一个要点就是全局变量不会主观上释放,但私有变量随着函数执行完毕后随之释放,所以每次除了被变量占据的函数由于不释放私有变量的值依然在累加沿用,其他类似于此题中fn()(3),中的i值都会随着fn释放重新取值;
想一下此类题还能怎么改?相信大家都会想起来,那就是函数传参数,就类似于隐含的私有变量而已,有兴趣的可以一试;
本人做题总结出此类题的小技巧:
做预解释或理解预解释时,1.先看全局是否有var某个变量(假设为f)占据着某个函数,若有则在进行f()时,f不释放;2.若某变量为自执行函数的返回值时,若有则返回,没有则返回undefined;3.在函数执行时看自己作用域内是否有私有变量,若没有则需要注意对全局变量的影响,同时更加需要注意的是函数的声明优先于变量的声明;
下篇继续来扯扯与面向对象原型相关的预解释,下章依然酸爽。。。。