理解js中的做用域,做用域链以及闭包

做用域
变量做用域的类型:全局变量和局部变量
全局做用域
对于最外层函数定义的变量拥有全局做用域,即对任何内部函数来讲,都是能够访问的数组

<script>
var outerVar = "outer"; function fn(){ console.log(outerVar); } fn();//result:outer
</script>

 

局部做用域
和全局用域相反,局部做用域通常只在固定的代码片断内可访问到,对于函数外部是没法访问的闭包

<script>
function fn(){ var innerVar = "inner"; } fn(); console.log(innerVar);// ReferenceError: innerVar is not defined
</script>

 


注意
须要注意的是,函数内部声明变量的时候,必定要使用var命令。若是不用的话,你实际上声明了一个全局变量!函数

<script>
function fn(){ innerVar = "inner"; } fn(); console.log(innerVar);// result:inner
</script>

 

做用域链
个人理解就是,根据在内部函数能够访问外部函数变量的这种机制,用链式查找哪些数据能被内部函数访问
执行环境
每一个函数在运行时都会产生一个执行环境。js为每一个执行环境关联了一个变量对象。环境中定义的全部变量和函数都保存在这个对象中
做用域链理解性能

<script>
var scope = "global"; function fn1(){ return scope; } function fn2(){ return scope; } fn1(); fn2(); </script>

当某个函数第一次被调用时,就会建立一个执行环境(execution context)以及相应的做用域链,并把做用域链赋值给一个特殊的内部属性([scope])。而后使用this,arguments(arguments在全局环境中不存在)和其余命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在做用域链的第0位。
以上面的代码为例,当第一次调用fn1()时的做用域链以下图所示: this

 

 能够看到fn1活动对象里并无scope变量,因而沿着做用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,因此就返回全局变量对象里的scope值。spa

闭包code

闭包有两个做用: 
第一个就是能够读取自身函数外部的变量(沿着做用域链寻找) 
第二个就是让这些外部变量始终保存在内存中 对象

关于第二点,来看一下如下的代码:blog

<script>
      function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){//注:i是outer()的局部变量
            result[i] = function(){ return i; } } return result;//返回一个函数对象数组
         //这个时候会初始化result.length个关于内部函数的做用域链
 } var fn = outer(); console.log(fn[0]());//result:2
      console.log(fn[1]());//result:2
   </script>

 

 返回结果很出乎意料吧,你确定觉得依次返回0,1,但事实并不是如此 
来看一下调用fn[0]()的做用域链图: ip

能够看到result[0]函数的活动对象里并无定义i这个变量,因而沿着做用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果。 
由此也能够得出,js函数内的变量值不是在编译的时候就肯定的,而是等在运行时期再去寻找的。

那怎么才能让result数组函数返回咱们所指望的值呢? 

<script>
      function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定义一个带参函数
            function arg(num){ function innerarg(){ return num; } return innerarg; } //把i当成参数传进去
            result[i] = arg(i); } return result; } var fn = outer(); console.log(fn[0]()); console.log(fn[1]()); </script>

 

由上图可知,当调用innerarg()时,它会沿做用域链找到父函数arg()活动对象里的arguments参数num=0. 
上面代码中,函数arg在outer函数内预先被调用执行了,对于这种方法,js有一种简洁的写法

function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定义一个带参函数
            result[i] = function(num){ function innerarg(){ return num; } return innerarg; }(i);//预先执行函数写法
            //把i当成参数传进去
 } return result; }

 

 

使用闭包的注意点

1)因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露。解决方法是,在退出函数以前,将不使用的局部变量所有删除。

2)闭包会在父函数外部,改变父函数内部变量的值。因此,若是你把父函数看成对象(object)使用,把闭包看成它的公用方法(Public Method),把内部变量看成它的私有属性(private value),这时必定要当心,不要随便改变父函数内部变量的值。

相关文章
相关标签/搜索