这一期很重要,涉及以后你对函数变量的理解,以及你调用函数不生效时怎么办,最主要的原因是讲的这些玩意面试时是一定会问的。
JS执行流程
1.预编译阶段,代码的检查装载阶段,此过程中进行变量和函数的声明,但是不对变量进行赋值,变量默认值为undefined
怎么理解,就是所有的声明(此时可以理解为变量声明),都提升到当前作用域最上面的位置,有变量名,但是没有值,就是声明不赋值,所以默认是undefined 。
例如我声明了一个变量:
var a=99;
console.log(a);
结果很正常就是99,那我们不声明变量,相当于直接:
console.log(a);
结果是not defined,我们再声明一个变量不赋值:
var a;
console.log(a);
由此我们得出:
not defined:没有声明就调用,找不到这个变量。
undefined:声明了变量,没有赋值,找不到这个变量。
那我把调用放前面:
console.log(a);
var a=99;
按道理说,代码从上到下执行,在前面找不到a相当于没有声明变量,不应该是not defined吗,为什么是undefined?这是因为函数执行两个阶段:
①预编译阶段,把所有的全局变量提升到作用域的最上面,只声明不赋值
②函数执行阶段
所以预编译阶段把var a=99;提升到了最上边,就成了undefined。
那好我再写个函数:
fn();
function fn(){
console.log('我是fn函数');
}
结果是什么?undefined吗?还是not defined?都不是
因为上面我没说,在函数的预编译阶段还有:
把整个函数提升到最上面,函数在变量之前。
还没完:
fn();
var fn = function(){
console.log('我是fn函数');
}
那这个结果是什么?
很好解释了,它把函数赋值给变量,把变量提升到最高,所以用函数的方法调用,当然就告诉你这不是一个函数了,如果改成console.log(fn);就又和原来一样是undefined了。
还没完!如果是下面这个东西会有什么结果:
var a =99;
function fn(){
console.log(a);
var a=100;
console.log(a);
} fn();
你可以叫它局部变量遮蔽,因为在预编译阶段也会在函数内部(局部),把局部变量提升到局部最高,
function fn(){
var a;相当于提升到这
console.log(a);//undefined
var a=100;
console.log(a);//100
}
提升完之后,它会向上找它就近的,这是一个就近原则。
2.代码的执行阶段,对变量进行赋值和函数的声明。
作用域链
前面也算是提过了,直接总结:
原则:①就近原则
②向上取值
③永不向下取值
解释一下向上取值,如果就近没有,就会向外取值,可能一解释反而糊涂了,就相当于上面说的局部作用域中没有,就会向上一级其父作用域中找。就像套娃一样,这个娃里没有,就到套它的娃里面找,以此类推。
闭包
闭包就是能够读取其他函数内部变量的函数。
像管道一样连接起函数内外。
比如这个函数
function fn(){
var a=100;
}
a是个局部变量,外边直接拿拿不到,有同学说可以return出来,好:
function fn(){
var a=100;
return a;
}
var a=fn();
console.log(a);
没问题,但这个方法,拿出来的只是这个值,后面会说垃圾回收机制,它会快速把这玩意给你清除掉,我们就不讨论了。
那怎么拿?我们刚说了作用域链,让它的子作用域向上取值给它带出来:
function fn(){
var a=100;
function fn2(){
console.log(a);
}
fn2();
}
fn();
好,思路没问题,那是不是可以让它儿子当个特务,给它return出来,
function fn(){
var a=100;
function fn2(){
return a;
}
return fn2();
}
console.log(fn());
打印fn( ),它要给你return出fn2( ),正好fn2要给你return a,又向上取值把a取出来,不这么写,直接return fn2这个函数,
function fn(){
var a=100;
function fn2(){
return a;
}
return fn2;
}
var fn3 =fn();
console.log(fn3());
取出fn2整个函数,再赋值调用,是不是也能取出来,还能写简单点:
function fn(){
var a=100;
return function(){
return a;
}
}
var fn3 =fn();
console.log(fn3());
整成匿名函数在前面return出去,也是可以的,这相当于:
var fn3 = function(){
return a;
} 赋值给了fn3,
其实这些靠卧底儿子取爹东西的写法就是闭包,因为return的结果不但把函数拿出来了,还连带把作用域return出来了,通过作用域链向上取出来。
闭包的好处
保护变量不被污染,保护什么变量?保护全局变量不被污染。
现在,把这句话牢牢记住:尽可能少的声明全局变量。
先要理解什么是污染,例如这有个函数:
var a =0;
function fn(){
a++;
console.log(a);
}
fn();
fn();
fn();
fn(); 结果很明显是1,2,3,4
但我写了很多东西,我在调用前有给它赋上别的值,后面就出错了,此时就对这个变量产生了污染。
此时我们只想让变量在那个函数内生效,那怎么做?用闭包:
function fn(){
var a =0;
return function(){
a++;
console.log(a);
}
}
a=2;
var fn1=fn();
fn1();
fn1();
不受影响,非常完美,就算再次赋值,调用也是从头开始,互相不受影响
闭包的缺点与解决办法
function a(){
return function(){
console.log('你好');
}
}
var a1=a();
var a2=a();
console.log(a1==a2);
a1为什么和a2不是一个东西?每次调用都开辟一块新空间,所以两者不同,a1 a2之间没有关系,保证了不被污染。
那缺点呢?
平时使用网页为什么它会越用越卡呢,因为每次调用不断的去开辟新空间,关闭页面对你的内存进行释放就不卡了,对于垃圾机制而言,用完后没用的变量它会进行一个回收,没有用的函数会被清空作用域。
但是关键在于一个没用,有用的肯定不清除,那如果它迟迟不清除,就会造成这个问题:
内存泄露
说个题外话,对于一些单机游戏,有很多外挂,改个金币属性什么的,它就是通过一些手段,从你内存里堆积的变量中找到对应的值改掉就行,很多游戏是用c需要来写的,上大学或多或少都学过,这是门比较难的语言,难就难在它的垃圾回收上,c语言没有垃圾回收机制,要手动清理,很多东西就会忘记清理,慢慢的堆积在那就形成了内存泄漏,所以就可以侵入到内存中找到对应的值修改就可以。
讲了这么多胡话,应该对内存泄露有了一个更直观的了解。
归根结底,一直保存在内存中就造成内存泄露,垃圾回收机制就是把没用的给你清理掉。
后面还会更准确的介绍两种垃圾回收机制的办法。
对于全局变量来说,只有在关闭页面的时候它会被回收,那垃圾回收机制为什么不把它清理掉,因为我们定义全局变量,就是为了在任意的时候来进行调用,所以不能随便回收,只要页面不关闭,它就会一直在页面中,就会造成内存泄露,这就是为什么要让大家尽量少声明全局变量。
那闭包为什么会造成内存泄露,就拿这个当例子:
function fn(){
var a =0;
return function(){
a++;
console.log(a);
}
} fn有一个变量,而且内部又有一个函数指向它,它既然有用为什么要清除它,那有同学说,把里面的变量清空不就行了,确实,但闭包完美的卡了个bug:闭包内的变量和参数都不会被回收。
这也就导致闭包这个东西不会被清空。
面试聊这个问题,下一个肯定是怎么解决闭包问题,解决方法也比较简单,想释放时,给该闭包函数,赋一个null值。
一赋值空就自动被回收了,
还是要提醒慎用闭包,忘记清空会让页面越用越卡。
js的基础到这里就弄完了,下面就是js的高级进阶学习了。
谢谢诸位陪伴,祝身体健康,事业有成。