JavaScript---闭包和作用域链

Posted bug达

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript---闭包和作用域链相关的知识,希望对你有一定的参考价值。

作用域和作用域链:

  参考文章 :http://www.cnblogs.com/malinlin/p/6028842.html    

        http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

        http://www.zhangyunling.com/?p=134

        https://segmentfault.com/a/1190000000652891

总结: 

  ① js中处处是对象

  ②函数执行时会创建一个执行环境和变量对象

  ③代码在执行环境中运行 变量对象会按照顺序存到作用域链中

  ④执行一次函数就会创建一个新的活动对象,就会有新的作用域链,多个作用域链互不干扰.

  ⑤引用函数不消失,活动变量就一直存在 , 闭包使用完了之后 将引用变量指向null,释放内存.

1. 全局作用域(Global Scope)
  (1)最外层函数和在最外层函数外面定义的变量拥有全局作用域
  (2)所有没有定义直接赋值的变量,自动声明为拥有全局作用域
  (3)所有window对象的属性拥有全局作用域,例如window.name、window.location  如下:定时器里指向的是全局的函数

    setTimeout("C()",1000)==setTimeout("this.C()",1000)

  

 1 var a = 1;
 2 function B(){
 3     var a = 2;
 4     setTimeout("C()",1000);
 5     setTimeout(C,2000);
 6     function C(){
 7         alert("a="+a);
 8     }
 9 }
10 function C(){
11     alert("a="+a);
12 }
13 B();

 


2. 局部作用域(Local Scope) 
  局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部

1. 执行环境和活动对象

  函数在执行时 生成执行环境变量对象 ,当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain)

  执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的变量对象(variable object),执行环境中定义的变量和函数就保存在这个变量对象中;
  全局执行环境是最外围的一个执行环境,通常被认为是window对象
  执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁).

  在闭包,每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁,但是活动对象由于被闭包函数引用,所以仍然保留,所以闭包使用完了之后 将引用变量指向null


3. 作用域链(Scope Chain)
   当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数的访问顺序;当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。

  作用域链的最前端,始终是当前代码执行环境的变量对象,如果这个环境是函数,则其活动对象就是变量对象
  作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境
  在函数执行过程,根据当前执行环境的作用域链来逐层向外查找变量,并且进行标识符解析

  这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。 它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象 包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会 被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。

   在函数执行过程中,没遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。

闭包:

  1.闭包可以访问函数中的变量。

  2.可以使变量长期保存在内存中,生命周期比较长。但闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即使用资源,将引用变量指向null。

<script>
    function A(){
        var x = 1;
        return function(){
            x++;
            console.log(x);
        }
    }
    var m1 = A();//第一次执行A函数
    m1();//2
    m1();//3
    var m2 = A();//第二次执行A函数
    m2();//2
    m1();//4
</script>

1.(为什么连续执行m1的时候,x的值在递增?)
answer:因为m1在引用的活动对象A一直没有释放(想释放的话可以让m1=null),所以x的值一直递增。
2.定义函数m2的时候,为什么x的值重新从1开始了?
answer:因为又一次运行了A函数,生成一个新的A的活动对象,所以m2的作用域链引用的是一个新的x值。
3.m1和m2里面的x为什么是相互独立,各自维持的?
answer:因为在定义m1和m2的时候,分别运行了A函数,生成了两个活动对象,所以,m1和m2的作用域链是指向不同的A的活动对象的。

好的,到这里先回顾一下前面说到的知识点:

  执行环境和变量对象在运行函数时生成
  执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁) 

 

闭包常见题目:

  

 1 function f1(){
 2     var n=999;
 3     nAdd=function(){n+=1}
 4     function f2(){
 5       alert(n);
 6     }
 7     return f2;
 8   }
 9   var result=f1();
10   result(); // 999
11   nAdd();   //首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,
12   result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

 

 

闭包中this的指向:

  

var name = "The Window";
var object = {
    name : "My Object",
    
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};

document.write(object.getNameFunc()());//The Window  匿名函数的执行环境具有全局性,因此其this对象通常指向window

 

以上是关于JavaScript---闭包和作用域链的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript---闭包和作用域链

JavaScript作用域链

JavaScript中的作用域 作用域链和闭包

javscript闭包的准备工作 -- 作用域与作用域链

JavaScript作用域链特性----闭包

JavaScript中的闭包和作用域链