`
35687638
  • 浏览: 9953 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

javascript函数(四) 作用域与闭包

阅读更多
下面的这些内容是翻译了《javascript权威指南》的部分内容:


4.6 变量vs属性
  或者你现在已经意识到,在jvascript中变量和对象的属性有很多的相同点。它们有相同的赋值方式,在表达中的使用方式也相同,等等。那么一个变量i和一个对象o的属性i有本质的区别吗?回答是没有。在javascript中变量和对象属性在本质上是相同的。
4.6.1全局对象
  当java解释器启动时,在执行javascript代码之前,它做的第一件事情是创建一个全局对象。这个全局对象的属性便是那些个程序的全局变量。当你定义一个全局变量就是给这个全局对象定义一个属性。
  javascript解释器使用一系列的内置值和函数来初使化全局对象的属性。例如将Infinity, parseInt,Math属性关联到数字的极限值,内置函数parseInt和内置对象Math。
  在顶层域中,你可以使用关键字this来引用这个全局对象。在函数内,this会有不同的用途。
  在客户端javascript中,这个全局对象是窗口对象,你可以使用window这个关键字而不是this来引用这个全局对象。
4.6.2局部变量:调用对象
  如果全局变量是这个特殊的全局对象的属性,那么局部变量呢?它们同样是对象的属性。这个对象被称为调用对象。调用对象的生命期比全局对象要短,但它们提供的功能是相同的。当函数体执行时,函数的参数和局部变量做为属性存在调用对象中。这样做是为了防止局部变量与全局变量产生名字冲突。
4.6.3javasciript的执行环境
  每次当javascript要执行一个函数时,它会为这个函数创建一个新的执行环境。所谓执行环境,就是说在这个执行环境中的所有javascript代码都将被执行。执行环境的一个重要组成部分是定义了那些变量的对象。所以,那些不在任何函数中的javascript代码是运行在将全局对象作为属性的执行环境中。每个函数都运行在自己唯一的运行环境中,这个运行环境拥有自己的调用对象。
  需要指出的一点是javascript的实现允许多个全局运行环境的存在,它们各自拥有不同的全局对象。(尽管如此,在这种情况下,每个全局对象并不完全是全局的)。一个很明显的例子是多个浏览器窗口同时开启,或者在一个浏览中存在着多个框架。
4.7 变量作用域
  当我们第一次讨论变量的作用域,我们是基于作用域:全局变量拥有全局作用域,局部变量拥有局部作用域。如果一个函数定义在另一个函数之内,变量被定义在内层函数体内。


下面的部分为转贴内容,同样是对《javascript权威指南》的翻译,顺便说一句,这是本好书,呵呵
8.8. 函数作用域与闭包
        如第四章所述,JavaScript函数的函数体在局部作用域中执行,局部作用域不同于全局作用域.本章将解释这些内容和相关的作用域问题,包括闭包.
  • 本章包含超前的内容,如果你是第一次阅读,可以跳过.

  • 8.8.1. 词法作用域(Lexical Scoping)
            JavaScript中的函数是基于词法作用域的,而不是动态作用域.这句话的意思是JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.定义一个函数时,当前作用域链被保存起来并成为该函数内部状态的一部分.作用域链的顶层(最初一层)是由全局对象构成的,这和词法作用域没什么明显的关联.然而,当你定义一个嵌套函数时,作用域链将包含外层函数(嵌套函数的外层函数.原文:the containing function).这就意味着,被嵌套的函数可以访问外层函数的所有参数和局部变量.

            注意:尽管在一个函数定义的时候,作用域链就已经固定了,但是作用域链中定义的属性并不是固定的.作用域链是"活的"("live"),当函数被调用的时候,它有权访问任何当前被关联的数据.

    8.8.2. 调用对象(The Call Object)
            当JavaScript解释器调用函数的时候,首先,它把作用域设置到作用域链,在函数被定义的时候,该作用域链已经有效.接下来,解释器添加一个叫做调用对象(ECMAScript规范使用术语:activation object,活动对象)的对象到作用域链的头部.引用Arguments对象的arguments属性为函数初始化调用对象.接下来,添加函数的命名参数到调用对象.所有用var语句定义的局部变量也都在这个对象中定义.因为调用对象在作用域链的头部,局部变量,函数参数和参数对象都在函数的作用域内.也就是说它们隐藏了所有同名的在更早的作用域中定义的属性.

    注意:与arguments不同,this是关键字,而不是调用对象的一个属性.

    8.8.3. 调用对象作为命名空间(The Call Object as a Namespace)
            有时,用定义一个简单函数的方法创建一个调用对象是很有用的,这个调用对象可以扮演一个临时命名空间的角色,如此一来,你定义的变量和创建的属性都不会破坏全局命名空间.例如:假设你有一个Javascrip代码文件,你希望把它用到很多不同的Javascript程序中(或者,用于客户端Javascript,在很多不同的web页上).假设这些代码像其它代码一样定义了中间变量来保存计算结果.现在的问题是因为这些代码将用于很多不同的程序,你无法知道此变量是否和其它引入该文件的程序的变量相冲突.

            解决的方法是把代码放到函数里,然后调用这个函数.如此一来,变量是被定义在函数的调用对象中:

    function  init()  {
         //  代码从这里开始
          //  任何变量声明都会成为调用对象的属性
          //  如此不会破坏全局命名空间.
    }
    init();   //  不要忘了调用这个函数哦!
            这段代码只给全局命名空间添加了一个"init"属性,该属性引用init函数.如果定义一个函数还嫌太多,那么你可以用一个表达式定义和调用一个匿名函数.像这样的JavaScript语法如下:

    ( function ()  {   //  这个函数没有名字.
          //  代码从这里开始
          //  任何变量声明都会成为调用对象的属性
          //  如此不会破坏全局命名空间.
    } )();           // 结束函数直接量,并调用该函数.

            注意:函数直接量外面的括号是JavaScript语法所必需的.

    8.8.4. 嵌套函数作为闭包(Nested Functions as Closures)
            JavaScript允许函数嵌套,允许把函数作为数据,允许使用词法作用域,把这些结合使用能创造出功能强大的令人惊奇的效果.让我们开始探索,考虑一下函数g被定义在函数f中.当f被调用的时候,作用域链由为函数f调用生成的调用对象跟随在全局对象之后构成.g函数被定义在f函数里,因此,这个作用域链作为g函数定义的一部分被保存起来.当g函数被调用的时候,作用域链包括三个部分:g函数自己的调用对象,f函数的调用对象和全局对象.

            嵌套函数在相同的它们被定义的词法作用域里被调用的时候是很容易理解的.例如,下面的代码并没有什么特别:

    var  x  =   " global " ;
    function  f()  {
         var  x  =   " local " ;
         function  g()  { alert(x); }
        g();
    }
    f();   //  调用这个函数显示 "local"
            然而,在JavaScript中,函数可以像其它值一样作为数据,因此可以在函数中返回一个函数,赋值给对象的属性,存储在数组中等等.这也没有什么特别的,除了嵌套的函数被调用的时候.考虑下面的代码,它包含一个返回嵌套函数的函数.每次被调用的时候,它都返回一个函数.被返回的函数的JavaScript代码总是相同的,但是,因为每次调用外层函数时的参数不同,每次被调用的时候,它(被返回的嵌套函数)创建的作用域也有些许不同.(也就是说,对于外层函数的每次调用,都会在作用域链中产生一个不同的调用对象.)如果你把返回函数保存在数组中,然后每一个调用一次,你将发现每一个函数都返回不同的值.因为每一个函数都由相同的JavaScript代码构成,并且每一次都是从相同的作用域中调用,所以,唯一能造成返回值不同的因素就是函数被定义的作用域:



    //  每次调用这个函数的时候返回一个函数
    //  函数被定义的作用域在每次调用时都不同
    function  makefunc(x)  {
         return   function ()  {  return  x; }
    }

    //  调用几次 makefunc() , 把结果保存到数组中:
    var  a  =  [makefunc( 0 ), makefunc( 1 ), makefunc( 2 )];

    //  现在调用这些函数并显示结果.
    //  尽管函数体是相同的,但是作用域是不同的,所以每次调用返回不同的结果:
    alert(a[ 0 ]());   //  Displays 0
    alert(a[ 1 ]());   //  Displays 1
    alert(a[ 2 ]());   //  Displays 2
            这段代码的结果是正确的,是根据词法作用域规则的严谨的应用所期待的:函数被执行在它被定义的作用域内.然而,这些结果令人吃惊的原因是,你期待的局部作用域在定义它们的函数退出的时候就不存在了.事实上,这是正常现象.当函数被调用的时候,解释器创建一个调用对象并把它放到作用域链的头部.当函数退出的时候,解释器从作用域链上删除这个调用对象.在没有嵌套函数被定义的时候,调用对象是唯一引用作用域链的对象.当调用对象从作用域链上删除时,就再也没有对它的引用了,它将被GC(garbage collected)回收.

            但是,嵌套函数改变了这些.如果嵌套函数被创建,这个函数的定义引用调用对象,因为这个调用对象是函数被定义的作用域链的顶部.如果嵌套函数只是被外层函数使用,对嵌套函数的唯一引用在调用对象里.当外层函数返回时,只有嵌套函数引用调用对象,调用对象引用嵌套函数,除此之外,再也没有其它的什么引用任何一个,因此,这两个对象就只能被GC使用了.

            如果你保存了一个嵌套函数的引用到全局作用域,情况就有所不同了.你把嵌套函数作为外层函数的返回值,或者把嵌套函数保存为其它对象的属性.在这种情况下,就有了一个对嵌套函数的外部引用,所以,嵌套函数在它的外部函数的调用对象中保持着它的引用.结果是,为外层函数调用生成的调用对象仍然有效,外层函数的参数和变量的名字和值也保留在这个调用对象里.JavaScript代码无法直接访问调用对象,但是,它定义的作为作用域链的一部分的属性仍用于嵌套函数的任何调用.(注意:如果外层函数保存了两个嵌套函数的全局引用,那么就有两个嵌套函数共享同一个调用对象,通过调用一个函数对调用对象的改变对另一个嵌套函数是可见的)

            JavaScript函数是被执行的代码和执行它们的作用域的组合.这个代码和作用域的组合在计算机科学著作中被称作:闭包(closure).所有的JavaScript函数都是闭包.然而,这些闭包只在象上面讨论的那样时才有趣:当一个嵌套的函数被输出到它被定义的作用域之外.只有嵌套函数被如此使用时,才被明确的称为闭包.

            闭包是有趣并且功能强大的技术.尽管它们不会被普通的使用在日常JavaScript编程中,它仍然值得我们去理解.如果你理解闭包,你理解作用域链和函数调用对象,那么,你才能真正的称自己为高级JavaScript程序员(JSer ).

    8.8.4.1. 闭包的例子(Closure examples)
            有时,你会想写一个函数,希望它能跨调用保存一个值.这个值不能保存在局部变量里,因为调用对象不会跨调用存在.全局变量是可以的,但是它会破坏全局命名空间.在8.6.3.章节中,我展现了一个名为uniqueInteger()的函数,它用一个属性保存这个恒久的值.你可以用闭包更进一步实现,创建一个恒久的私有的变量.下面是不用闭包写的一个函数:


    //  每次调用返回一个不同的整数
    uniqueID  =   function ()  {
         if  ( ! arguments.callee.id) arguments.callee.id  =   0 ;
         return  arguments.callee.id ++ ;
    } ;
            这种方法的问题在于任何人都能设置这个uniqueID.id为0,而破坏了该函数不能返回同一个值两次的约定.你可以通过保存这个恒久值到一个只有你自己的函数有权访问的闭包里的方法来防止别人设置:

    uniqueID  =  ( function ()  {   //  这个函数的调用对象保存值
          var  id  =   0 ;            //  这是私有恒久的那个值
          //  外层函数返回一个有权访问恒久值的嵌套的函数
          //  那就是我们保存在变量uniqueID里的嵌套函数.
          return   function ()  {  return  id ++ ; } ;   //  返回,自加.
    } )();  //  在定义后调用外层函数.
            例子8-6是第二个闭包的例子.它示范的是像第一个一样的私有恒久变量,但是这个能被多个函数共享.

            Example 8-6. Private properties with closures

    //  这个函数为对象o的指定名称的属性添加了访问方法
    //  方法名为:get<name>和set<name>.
    //  如果提供了一个判断函数,setter方法将在保存前判断参数是不是有效的
    //  如果检验失败,setter方法抛出一个异常
    //  这个函数的与众不同之处在于,用getter和setter方法操作的属性值并不是存储在对象o里面,
    //  相反的,值被存储在函数的局部变量里.
    //  getter和setter方法也被定义为函数的局部方法,因此有权访问这个局部变量.
    //  注意:对于两个访问方法,该值是私有的,除了setter方法,无法修改或设置它.
    function  makeProperty(o, name, predicate)  {
         var  value;   //  This is the property value

         //  getter方法只是简单的返回值.
         o[ " get "   +  name]  =   function ()  {  return  value; } ;

         //  setter保存值,如果校验失败则抛出异常
         o[ " set "   +  name]  =   function (v)  {
             if  (predicate  &&   ! predicate(v))
                 throw   " set "   +  name  +   " : invalid value  "   +  v;
             else
                value  =  v;
        } ;
    }

    //  下面的代码演示makeProperty() 方法.
    var  o  =   {} ;   //  这是一个空对象

    //  添加属性访问方法getName() 和 setName()
    //  确保只允许字符串值
    makeProperty(o,  " Name " ,  function (x)  {  return   typeof  x  ==   " string " ; } );

    o.setName( " Frank " );   //  设置属性值
    print(o.getName());   //  获得属性值
    o.setName( 0 );         //  试图设置错误类型的值
            我知道的最简单最有用的使用闭包的例子是Steve Yen创建的断点程序,它发布在 http://trimpath.com ,是TrimPath客户端框架的一部分.断点是函数内的一个点,代码执行到该点停止,给程序员检查变量,表达式,调用函数等的值的机会.Steve的断点技术用闭包捕捉函数的当前作用域(包括局部变量和函数参数),用全局的eval()函数组合这些就可以检查作用域了.eval()函数计算JavaScript代码字符串并返回结果.下面是一个以自检闭包方式工作的嵌套函数.


    // 捕捉当前作用域,可以用eval()检查
    var inspector = function($) { return eval($); }

            这个函数用了很少见的标识符$作为参数名,这样可以减少在计划检查的作用域内命名冲突的可能性.

            (接下来部分代码与所述内容无关,译略)

    8.8.4.2. 闭包和IE中的内存泄露(Closures and memory leaks in Internet Explorer)
            MS的IE浏览器在ActiveX对象和客户端DOM元素的GC方面表现较弱.客户端对象按引用计数,当引用数为0的时候释放对象.这种方法在循环引用的时候就失效了,例如,当一个核心JavaScript对象引用一个文档元素,而那个文档元素又有一个属性(比如是一个事件句柄)引用该核心JavaScript对象.

            在IE客户端编程使用闭包的时候,这种循环引用经常出现.当你使用闭包的时候,记住,封闭(enclosing)函数的调用对象,包括函数所有的参数和局部变量,都将和闭包一样"长寿".如果任何函数参数或者局部变量引用了一个客户端对象,就会发生内存泄露.
    分享到:
    评论

    相关推荐

      JavaScript: 函数与作用域深入解析及应用场景

      接着讨论了 JavaScript 中的全局作用域、局部作用域、块级作用域和函数作用域,特别是闭包的概念。随后,文章探讨了函数的高级用法,如递归函数、高阶函数和立即执行函数表达式(IIFE)。最后,通过实际应用示例,如...

      javascript 闭包、匿名函数、作用域链

      JavaScript中的闭包、匿名函数和作用域链是编程中至关重要的概念,它们是理解JavaScript运行机制的关键。在本文中,我们将深入探讨这三个概念,并通过实际示例来展示它们的运用。 首先,我们来讨论“闭包”。闭包是...

      JS的作用域与闭包

      闭包是JavaScript中一个高级概念,它允许一个函数记住并访问它外部作用域中的变量,即使该函数在其外部作用域之外执行也是如此。 ##### 1. 闭包的定义 闭包是由函数和与其相关的引用环境组合而成的实体,这个环境...

      深度探讨javascript函数的原型链和闭包

      `)仅在该函数作用域内可见,而外部变量(如`out_var`)则可以通过作用域链在内部访问。闭包是理解作用域链的关键概念,它允许内部函数记住其定义时的作用域,即使外部函数已经完成执行。闭包常常用于封装变量和实现...

      【JavaScript源代码】JS难点同步异步和作用域与闭包及原型和原型链详解.docx

      全局作用域的变量在整个脚本中都可访问,而函数作用域的变量只在其定义的函数内可见。ES6引入了块级作用域,使得在`{}`中的`let`和`const`声明的变量只在其所在的代码块内有效。闭包是一种特殊的作用域现象,它允许...

      理解javascript函数式编程中的闭包(closure)_.docx

      闭包的产生是因为JavaScript的函数作用域规则。当内部函数引用了外部函数的变量时,JavaScript会保留这些变量的状态,以便内部函数在后续调用中继续访问。在这个例子中,`counter`实际上是一个闭包,因为它包含了`...

      JavaScript中的作用域链和闭包

      执行上下文有一个自己的作用域链,这个链是在函数作用域链的基础上初始化的。 **活动对象(Active Object)**是执行上下文的一部分,它包含了函数的所有局部变量、参数和`this`。当函数执行时,活动对象被创建并推...

      JavaScript:函数与作用域

      ### JavaScript:函数与作用域 #### 一、函数基础 **1.1 函数声明与表达式** 在JavaScript中,函数是一段可重用的代码块,用来执行特定任务。函数可以通过两种方式进行定义:函数声明和函数表达式。 - **函数...

      javascript中的作用域和闭包详解

      闭包的创建通常涉及嵌套函数,内层函数对外层函数作用域中变量的引用形成了闭包。在创建闭包时,需要注意内存泄漏的问题,尤其是在循环中创建闭包时,如果将外部变量的引用暴露给了全局变量,这可能会导致内存泄漏。...

      Web前端面试题目JavaScript(作用域,原型。原型链,闭包,封装函数).txt

      前端面试题,包含JavaScript的闭包,作用域,原型,原型链,上下文环境以及DOM,BOM封装函数深度克隆,以及一些常见的·JS问题,试题简单但是容易混淆,作为前端工程师必考题

      原型、作用域、闭包的完整解释(一)

      在JavaScript编程语言中,原型(Prototype)、作用域(Scope)和闭包(Closure)是三个核心概念,对于理解和编写高效、可维护的代码至关重要。本文将深入探讨这三个概念,并通过实例解析它们的工作原理。 首先,...

      JavaScript作用域、闭包、对象与原型链概念及用法实例总结

      - **函数作用域**:JavaScript的变量作用域主要分为函数作用域和全局作用域。函数作用域意味着变量在其定义的函数内部有效。在函数内部定义的变量不能在函数外部访问,而函数外部定义的变量可以在函数内部访问。 -...

      JavaScript函数式编程.pdf

      闭包是指有权访问另一个函数作用域中变量的函数。由于JavaScript的作用域链,闭包能够访问到函数定义时的外部变量,即使外部函数已经执行结束。闭包通常用于创建私有变量和方法,以及数据封装。 4. 纯函数和副作用 ...

      深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

      本文将深入探讨JavaScript中的函数、递归和闭包,以及执行环境、变量对象与作用域链的概念。 首先,我们来看JavaScript中定义函数的两种方式:函数声明和函数表达式。函数声明是一种明确的、独立的代码结构,例如`...

      JavaScript 中的闭包是指内部函数可以访问外部函数作用域中的变量

      这一特性是基于JavaScript的函数作用域规则以及函数本身可以作为值进行传递的特点所形成的。 闭包的形成依赖于以下几点: 1. **函数作用域**:在JavaScript中,变量的作用域由其声明的位置决定。如果一个变量是在...

      浅谈JavaScript中的作用域和闭包问题

      当函数被定义在另一个函数内部时,内部函数将持有外部函数作用域的引用,即使外部函数已经执行完毕,只要内部函数还在使用这些变量,它们就不会被垃圾回收机制回收。这使得闭包在创建私有变量和方法时非常有用,它还...

      javascript 函数及作用域总结介绍

      值得注意的是,JavaScript没有块级作用域的概念,直到ES6引入let和const关键字之前,使用var声明的变量都是函数作用域或全局作用域。 在讨论作用域时,还必须提到变量提升(hoisting)的概念。变量提升指的是在...

      javascript 词法作用域和闭包分析说明

      闭包的作用域链包含了它自身的作用域以及所有包含它的函数作用域,因此闭包可以记住并访问外部函数的作用域,即使外部函数已经执行完毕。理解闭包的关键在于理解函数是如何携带其词法作用域的。 从给定文件的描述和...

      JavaScript闭包函数

      闭包是ECMAScript (JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包。闭包的创建相对容易,人们甚至会在...而闭包工作机制的实现很大程度上有赖于标识符(或者说对象属性)解析过程中作用域的角色。

      005课-继承作用域闭包.rar

      此外,JavaScript还引入了块级作用域的概念(通过let和const关键字),以及函数作用域和词法作用域的概念。词法作用域是指函数的执行上下文由其定义时的位置决定,而不是调用时的位置。 **闭包** 闭包是JavaScript...

    Global site tag (gtag.js) - Google Analytics