Js整理备忘(06)——函数基础(二) 作用域与闭包

——这一部分比较难理解,第一遍看不懂应该是正常的(嘻嘻,为自己一开始看书没看懂找个借口*_*),初学时可以先跳过,或者先知道个大概。

1、作用域相关的一些概念:

  • Javascript(以下简称Js)解析器启动时就会初始化创建一个全局对象(global object,以下统称global对象),这个global对象拥有一些全局属性和方法,如NaN(非数值常量)、parseFloat(将字符串转换为浮点数值的方法)、Math(内建的数学函数对象)、以及var定义的全局变量。在客户端Js中,Window就是这个global对象(也可以叫全局作用域)。
  • 关于作用域的概念,已经知道变量的作用域有局部和全局两种,由变量定义时的环境决定。而对于函数来说不这么简单,它有一个作用域链(scope chain这是一个对象链。函数在调用时可以访问对象链上所有绑定的属性值。
  • 函数在定义时,当前作用域链就被保存在该函数内部。若函数定义在global环境中,则它的作用域链在定义时仅由global对象组成。若函数作为一个嵌套函数(定义在另一个函数体内),则该嵌套函数的作用域链就包含其外层函数,它可以访问其外层函数的所有参数和变量。
  • 虽然函数定义时作用域链固定,但是作用域链上定义的属性并没有固定,这要取决于函数的调用。一个函数允许多次调用,且允许传递不同的参数。函数每次调用时,都是运行在定义时的作用域链上,只是绑定的属性值不同而已。

2、函数的调用——进一步理解函数作用域

  • 函数在每次调用时都会生成一个临时的调用对象(call object,以下统称call对象),这个call对象被添加到函数定义时的作用域链的头部,作为本次调用默认的作用域。该函数内的局部变量以及参数都成为这个call对象的属性(如arguments属性)。调用对象:ECMAScript规范术语称之为activation object(活动对象)。

(1)若在全局环境中调用函数,则调用时的作用域链为“call对象—>global对象”。看下面两个例子

例一:

<scripttype="text/javascript">
    function
print(msg) {
        document.write(msg + "<br/>");
    }
    var s = "hello";
    functionf(a) {
        print(this.s + ","+ a);
    }
    f("js");   
</script>

输出结果:hello,js

 

scope1

例二:

<script type="text/javascript">
    function print(msg) {
        document.write(msg + "<br/>");
    }
    var s = "hello";
    function f(a) {
        var s = "nihao";
        print(s + "," + a);
    }
    f("js");    
</script>

输出结果:nihao,js

 


scope2
 

分析:

例一:在调用f时,需要输出s跟a的值,则先查找默认的域,由于默认域(call对象)中找不到s的定义,则会继续沿着作用域链向上找,直到发现global对象中有s的定义,才返回找到的值。若找到最顶层也没有找到s的定义,则会返回undefined。

例二:作了一点修改,在函数中加入了s的定义,那么该函数调用时就只需要在默认域中运行。

(2)如果函数f作为嵌套函数,则调用f时的作用域链就是“call对象(f)-->call对象(外层函数)-->global对象”,头部的call对象(f)作为该嵌套函数调用时默认的域。

 

  • 一般来说,函数完成一次调用之后,调用时初始化的局部变量和参数应该不再存在。调用时创建的call对象在函数调用结束后也就应该被销毁。但是Js允许定义嵌套函数,那么这个域(指嵌套函数的call对象)就有可能在该嵌套函数调用结束之前被外部引用,并且这个引用没有随着函数调用的结束而结束。这时,这个嵌套函数的域就不会被销毁,也不应该被销毁。这种情况下,该嵌套函数就创建了闭包(closure

 

3、闭包(closure)

闭包是啥???——以下摘自百度百科

闭包:基本概念

  闭包是可以包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量的存在,相关引用没有释放)和为自由变量提供绑定的计算环境(作用域)。在 Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby 和 Python 等语言中都能找到对闭包不同程度的支持。

  闭包的价值在于可以作为函数对象 或者匿名函数,对于类型系统而言这就意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中、作为参数传递给其他函数,最重要的是能够被函数动态地创建和返回。

Js中闭包的特点:

1、作为一个函数变量的一个引用,当函数返回时,其仍然处于激活状态。

2、一个闭包就是当一个函数返回时,没有释放资源的栈区。

其实,概念表述得蛮清楚了,不过要建立在熟悉函数操作的基础上。下面看个实现闭包的例子:

4、一个闭包的例子

——摘自《Javascript王者归来》,很不错的一本书

<script language="javascript" type="text/javascript">
    function print(msg) {
        document.write(msg + "<br/>");
    }    
    //定义一个抛物线函数y=ax^2+bx+c
    function parabola(a, b, c) {
        var ret= function(x) {
            return a * x * x + b * x + c;
        }        
        ret.toString = function() { //重写toString()方法
            return a + "x^2 + " + b + "x + " + c;
        }
        return ret; //返回函数ret
    }
    var y = new parabola(3, 4, -7); //创建一个抛物线对象实例
    print(y + "(x=4时)-->" + y(4));
</script>

结果输出:3x^2+4x+-7(x=4时)—> 57

代码分析:

代码中定义了一个抛物线函数parabola,内部定义了一个嵌套的匿名函数并赋给它的局部变量ret,最后返回ret的值,即返回了匿名的嵌套函数的引用。

var y = new parabola(3, 4, -7);这句代码执行时须调用parabola函数,会在该函数的作用域链头部添加一个临时的call对象(parabola的默认域),当运行到内部嵌套的匿名函数时,又会生成一个call对象(嵌套函数的域)。此时的作用域链应该如下图所示:

closure

当创建对象实例y的语句执行完成后,嵌套函数的引用ret作为返回值赋给y(此时y=3x^2+4x-7),就是说此时该嵌套函数仍然处于活动状态,因为函数中还包含一个自由变量x。而且嵌套函数可以操作其外层函数中所有的属性,所以外层函数也处于活动状态。所以外层parabola函数的调用看似执行完成,但是这个作用域还不能销毁,因为其中的x属性还处在活动状态。

——这种情况下就说这个嵌套函数创建了一个闭包。

——另:该例实现了函数式编程(functional programming),不在本文的讨论范围。

原文地址:https://www.cnblogs.com/gppblog/p/1648639.html