在我们写代码写到一定阶段的时候,就会想深究一下js,javascript是一种弱类型的编程语言,而js中一个最为重要的概念就是执行环境,或者说作用域。作用域重要性体现在哪呢?首先,函数在执行时会创建作用域链,我们可以通过作用域链,向上一层一层的找到我们想要的变量。其次,搞清楚作用域,有利于深层次的理解闭包。再次,可以写出更加优化的代码,提高执行速度,防止内存溢出。可以说,作用域是一个核心。
如果大家知道c语言,可以知道块级作用域的特点,每一块都是局部的作用域,在if(){}外边是访问不了if里面的变量的。但是js没有块级作用域概念,相对的是函数的作用域(执行环境)。如果我们把js跟c或c++结合起来理解的话,函数的执行就容易理解。
相信大家都知道,作用域(执行环境)分为全局作用域和局部作用域。在函数外部定义的变量或函数,都是具有全局作用域,而函数里面定义的变量,则具有局部作用域。
任何函数的执行依赖于函数中的变量作用域,变量作用域是在函数定义时决定的,而不是在函数执行时决定的。
先说说普通函数的执行。
function fn1(){ var test = 1; fn2(); alert(test); } function fn2(){ test++; } fn1();
那结果是几呢?对不起,会直接报错。这就是上面所说的,虽然fn2执行时,会创建执行环境,但是fn2的作用域链中并没有fn1,因为fn2中的test的作用域是在fn2函数定义时确定的,画图表示就是:
从这个作用域链上,可以看出来,fn2在执行时,本身找不到test变量,则会顺着作用域链向上找,上层作用链就是全局变量,而不是fn1的作用域里的变量。在全局变量对象中找不到test变量,那么就报错了。
那么如果把代码变化一下:
function fn1(){ var test = 1; (function fn2(){ test++; }()) alert(test); } fn1(); //2 fn1(); //2
这时结果就不会报错了,稍稍改动,变化如此之大,这是为什么?相信大家很清楚了。再画一下作用域链图:
在上面的图中出现了活动对象,什么是活动对象?
当fn1执行时,fn1会被压入栈中执行,这时会创建fn1的执行环境(作用域)。然后会使用arguments和参数、变量初始化活动对象。这个活动对象也就是变量对象。
,当执行到fn2时,又会把fn2函数压入栈顶,执行fn2,创建执行环境、作用域链、活动对象,当fn2执行完成后,fn2的执行环境就会销毁,作用域链和活动对象也会被销毁,然后把执行权还给fn1执行。执行到alert(test),把2弹出。
当是闭包时,情况就有很大的不同。(从技术上,每个函数都是一个闭包,这里的闭包讨论的是一个函数返回另外一个函数的情况)
function outer(){ var a= 0; return function inner(){ return a++; } } var result = outer(); console.log(result()); //0 console.log(result()); //1 console.log(result()); //2
这个结果不是0,而是a进行了累加。这的原理又是什么呢?
原因还是函数的作用域链是在定义时确定的,而且在函数执行时仍然存在。
分析一下执行的步骤:
当执行outer()时,会把outer函数推入栈,当return时,返回的是一个函数而不是一个数值。这个inner()函数在定义时就有作用域链,作用域链中有外层outer函数中的变量,当outer返回后,inner()函数的作用域链还存在着,也就是我们平时说的inner函数占用了outer函数中的a变量。那为什么inner函数的作用域链中会有outer函数的变量呢?原因是在一个函数内部定义的函数会将包含函数(即外层函数)的活动对象添加到它的作用域链中。
再在上面的基础上继续变化一下,看看结果如何变化:
function outer(){ var a= 0; return function inner(){ return a++; } } var result = outer(); console.log(result()); //0 console.log(result()); //1 console.log(result()); //2 var ox = outer()(); console.log(ox);
这时,console.log(ox)的值是多少呢? 答案是0,而不是3
原因也很简单,函数每次运行,都会产生独立的执行环境(作用域),也就是说两次outer()的执行没有关系。上一次outer()执行完以后,它的执行环境早就已经释放了。
使用闭包有明显的好处,可以分别保存变量的值。特别在处理一些点击事件时,需要分别保存不同的变量值。但是闭包还是要慎用,因为闭包会使一些变量无法释放,占用内存,不利于优化。
闭包优化方法:
在我们不再使用用返回的值是,将其置为null
如把result和ox置为null,就可以解除对函数的引用,释放内存。
function outer(){ var a= 0; return function inner(){ return a++; } } var result = outer(); console.log(result()); //0 console.log(result()); //1 console.log(result()); //2 var ox = outer()(); console.log(ox); result = null; ox = null;
以上是我对作用域和闭包的一些认识,有错误或不足的地方欢迎大家指正!