资讯专栏INFORMATION COLUMN

函数作用域与闭包

Code4App / 3147人阅读

摘要:函数作用域要理解闭包,必须从理解函数被调用时都会发生什么入手。可以说,闭包是函数作用域的副产品。无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

函数作用域

要理解闭包,必须从理解函数被调用时都会发生什么入手。

我们知道,每个javascript函数都是一个对象,其中有一些属性我们可以访问到,有一些不可以访问,这些属性仅供JavaScript引擎存取,是隐式属性。[[scope]]就是其中一个。
[[scope]]就是我们所说的作用域,其中存储了执行期上下文的集合。由于这个集合呈链式链接,我们把这种链式链接叫做作用域链。

当函数被定义(创建)时有一个自己所在环境的作用域(GO全局作用域 ,若是在函数内部,就是引用别人的作用域),当函数被执行时,会将自己的独一无二的AO(活动对象,是使用arguments和该函数内部的变量值初始化的活动对象)执行上下文放在前端,形成一个作用域链;当该函数执行完,自己的AO会被干掉,回到被定义时的状态。

另外,变量的查找,就是找所在函数的作用域,首先从作用域的顶端开始查找,找不到的情况下,会查找外部函数的活动对象,依次向下查找,直到到达作为作用域链终点的全局执行环境。

下面看几个查找变量例子,深入理解函数作用域及作用域链。

function a(){
   function b(){
      var b=2223;  
   }
   var a=78;
}
a()
b()
console.log(b)

输出结果: error: b is not defined
当函数a执行完毕后,该函数内部的活动对象AO就会被销毁。所以函数外部是访问不到函数内部的变量的。

function outer(){
   function inner(){
      var b=2223;   
      a=0
   }
   var a=78;
   inner() //①
   console.log(a) 
   console.log(b)
}
outer() 

输出结果: 0 , error: b is not defined

当函数inner在被定义的阶段,就会拥有(引用)函数outer的作用域(包括函数outer自己局部的活动对象AO和全局作用域);当函数inner()被执行的时候,会再创建一个自己的活动对象AO并被推入执行环境作用域链的前端。
inner()函数在被执行的时候,由于变量a在outer()函数中已经存在并被inner()引用,所以inner()函数内部的变量a会修改掉外部函数变量a的值,并且可以不用声明。当inner()函数执行完毕后(执行到①处),inner()函数局部的AO会被销毁,下面就访问不到变量b了,而且这时候变量a的值将是被inner()函数修改过的值。

function a(){
  function b(){
     var b=2223;   
     a=0
  }
  var a=78;
  b=1
  b()
  console.log(b)
}
a()

输出结果: 1

var x=10;
function a(){
    console.log(x);
}
function b(){
    var x=20;
    a();
}
a();//10
b();//还是10;

总之:函数在被定义阶段,会引用着其所在环境的作用域;执行阶段,会创建一个自己独一无二的活动对象AO,并推入执行环境作用域链的前端;函数执行完毕之后,自己的执行上下文AO会被销毁,所以,这时候访问其内部的变量是访问不到的。但是,闭包的情况又有不同。

简述什么是闭包

闭包:有权访问另一个函数作用域中的变量的函数。

从理论的角度上,所有的JavaScript函数都是闭包,因为函数在被定义阶段就会存储一个自己所在环境的作用域,可以访问这个作用域中的所有变量。可以说,闭包是 JS 函数作用域的副产品。理解js作用域,自然就明白了闭包,即使你不知道那是闭包。

从技术实践的角度,以下函数才算闭包:

定义该函数的执行环境的作用域(执行上下文)即使被销毁 ,它的活动对象(AO)仍然会留在内存中,被该函数引用着。

引用了函数体外部的变量。

创建闭包常见方式,就是在一个函数A内部创建另一个函数B,然后通过return这个函数B以便在外部使用,这个函数B就是一个闭包。

举个例子:

//也算闭包
var a = 1;
function foo() {
    console.log(a);
}
foo();

//函数内部定义函数
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()()  //这里相当于:
//var foo = checkscope();
//foo();

输出结果:local scope
f()函数在被定义阶段就被保存到了外部,这个时候就相当于外部的函数可以访问另一个函数内部的变量,f()函数会形成一个闭包。

按照函数作用域的概念,当checkscope()执行完毕后,其局部的活动对象AO会被销毁;但是由于checkscope()函数执行完毕后返回一个函数,根据函数在被定义阶段会引用该函数所在执行环境的执行上下文,被返回的函数f()即使被保存到了外部依然引用着checkscope()函数的执行期上下文,直到函数f()执行完毕,checkscope()函数的执行上下文才会被销毁。

也就是说被嵌套的函数f()无论在什么地方执行,都会包含着外部函数(定义该函数)的活动对象。所以,即使f()被保存到外部,也可以访问到另一个函数checkscope()中定义的变量。

无论通过何种手段将内部函数传递到所在的词法作用域以外, 它都会持有对原始定义作用域的引用, 无论在何处执行这个函数都会使用闭包。

由此看来,闭包可能会导致一个问题:导致原有作用域链不释放,造成内存泄漏(内存空间越来越少)。可以通过手动将被引用的函数设为null,来解除对该函数的引用,以便释放内存。

闭包的作用

实现公有变量。

function a(){
   var num=100;
   function b(){
      num++;
      console.log(num)
   }
   return b;
}

var demo=a();
demo();//101
demo();//102
function test(){
   var num=100;
   function a(){
      num++;
   }
   function b(){
      num--;
   }
   return [a,b]

}

var demo=test()
demo[0]();//101
demo[1]();//100
//函数a和函数b引用的是同一个作用域。

实现私有变量。

闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的函数接口来操作。
通过在立即执行函数中return 将方法保存到外部等待调用,内部的变量由于是私有的,外部访问不到,可防止污染全局变量,利于模块化开发。

var foo = ( function() { 
   var secret = "secret"; 
   // “闭包”内的函数可以访问 secret 变量,而secret变量对于外部却是隐藏的 
   return { 
      get_secret: function () { 
         // 通过定义的接口来访问 secret 
            return secret; 
      }, 
      new_secret: function ( new_secret ) { 
         // 通过定义的接口来修改 secret 
         secret = new_secret; 
      } 
   }; 
} () ); 
foo.get_secret (); // 得到 "secret" 
foo.secret; // undefined,访问不能 
foo.new_secret ("a new secret"); // 通过函数接口,我们访问并修改了secret 变量 
foo.get_secret (); // 得到 "a new secret"
var name="bcd";
var init=(function (){
   var name="abc";
   function callName(){
      console.log(name);
   }

   //其他方法

   return function () {
      callName();
      //其他方法
   }
}())

init () //abc
闭包经典题
function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(){
      console.log(i);
     };
  }
return result;
}

var fun = createFunctions();
for(var i=0;i<10;i++){
    fun[i]();
}

输出结果:打印十个10
数组每个值都是一个函数,每个函数对createFunctions()形成一个闭包,此时i都是引用createFunctions()中同一个i变量。

function test(){
   var arr=[];
   for(var i=0;i<10;i++){
      (function(j){
         arr[j]=function(){
            console.log(j);
         }
      }(i))
   }
   console.log(i)//10 ,i还是10
   return arr
}
var myArr=test();
for(var i=0;i<10;i++){
   myArr[i]()
}

输出结果:从0到9
这次依然把数组每个值赋为函数,不同的是循环十次立即执行函数,并将当前循环的i作为参数传进立即执行函数,由于参数是按值传递的,这样就把当前循环的i保存下来了。

闭包中的this对象

在闭包中使用this对象可能会导致一些问题,结果往往不是预想的输出结果。
看个例子:

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

输出结果:The Window

this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。匿名函数往往具有全局性,这里可以这样理解,没有任何对象调用这个匿名函数,虽然这个匿名函数拥有getNameFunc()的执行上下文。

因为这个匿名函数拥有getNameFunc()的执行上下文,通过把外部函数getNameFunc()作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问到该对象了。

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

输出结果:My Object

理解到这里,基本上就搞定了闭包了。

学习资料

JavaScript 里的闭包是什么?应用场景有哪些?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93121.html

相关文章

  • Javascript重温OOP之作用域与闭包

    摘要:的变量作用域是基于其特有的作用域链的。需要注意的是,用创建的函数,其作用域指向全局作用域。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 作用域 定义 在编程语言中,作用域控制着变量与参数的可见性及生命周期,它能减少名称冲突,而且提供了自动内存管理 --javascript 语言精粹 我理解的是,一个变量、函数或者成员可以在代码中访问到的范围。 js的变量作...

    JessYanCoding 评论0 收藏0
  • Javascript中的作用域与闭包

    摘要:作用域分为词法作用域和动态作用域。这样就形成了一个链式的作用域。一般情况下,当函数执行完毕时,里面的变量会被自动销毁。而能够访问到这个在的编译阶段就已经定型了词法作用域。 什么是作用域?在当前运行环境下,可以访问的变量或函数的范围。作用域分为词法作用域和动态作用域。词法作用域是在js代码编译阶段就确定下来的; 对应的,with和eval语句会产生动态作用域。 会产生新的作用域的情况: ...

    tianren124 评论0 收藏0
  • Js基础知识(三) - 作用域与闭包

    摘要:是词法作用域工作模式。使用可以将变量绑定在所在的任意作用域中通常是内部,也就是说为其声明的变量隐式的劫持了所在的块级作用域。 作用域与闭包 如何用js创建10个button标签,点击每个按钮时打印按钮对应的序号? 看到上述问题,如果你能看出来这个问题实质上是考对作用域的理解,那么恭喜你,这篇文章你可以不用看了,说明你对作用域已经理解的很透彻了,但是如果你看不出来这是一道考作用域的题目,...

    lemanli 评论0 收藏0
  • Js基础知识(三) - 作用域与闭包

    摘要:是词法作用域工作模式。使用可以将变量绑定在所在的任意作用域中通常是内部,也就是说为其声明的变量隐式的劫持了所在的块级作用域。 作用域与闭包 如何用js创建10个button标签,点击每个按钮时打印按钮对应的序号? 看到上述问题,如果你能看出来这个问题实质上是考对作用域的理解,那么恭喜你,这篇文章你可以不用看了,说明你对作用域已经理解的很透彻了,但是如果你看不出来这是一道考作用域的题目,...

    XFLY 评论0 收藏0
  • Js基础知识(三) - 作用域与闭包

    摘要:是词法作用域工作模式。使用可以将变量绑定在所在的任意作用域中通常是内部,也就是说为其声明的变量隐式的劫持了所在的块级作用域。 作用域与闭包 如何用js创建10个button标签,点击每个按钮时打印按钮对应的序号? 看到上述问题,如果你能看出来这个问题实质上是考对作用域的理解,那么恭喜你,这篇文章你可以不用看了,说明你对作用域已经理解的很透彻了,但是如果你看不出来这是一道考作用域的题目,...

    tanglijun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<