`
DBear
  • 浏览: 231842 次
  • 性别: Icon_minigender_2
  • 来自: 上海
社区版块
存档分类

Javascript精炼---Functions作用域及闭包

阅读更多

一、作用域(Scope)

1、scope chain

      每个JavaScript执行环境(某教程对于“执行环境”的解释是:Js解释器每次执行一个function时,就会为该function创建一个新的执行环境。)都关联一个scope chain。scope chain是由一组对象组成的链表,当Js代码需要查找变量x的值时,会首先从此链表的第一个对象查起,如果该对象含有名为x的属性,则取用该属性值,如果没有,则继续向下一个查找。

      在顶层Js代码(比如,不包含在任何function中的代码)中,scope chain仅由一个对象组成,那就是global object;在一个非嵌套function中,scope chain由两个对象组成,第一个是function的call object,第二个是global object,所以当在该function内去某变量时,首先查找的是call object,其次是global object;在一个嵌套function(nested function,定义在其它function中的function,有时也成内部function)中,scope chain由三个以上的对象组成,第一个是该function的call object,其次是它上一层funciton的call object,以此类推,最后是global object。

2、function scope

     JavaScript中的function都是lexically scoping(也称static scoping,与dynamically scoping对应)。也就是说,function的作用域在function定义时就被确定而非运行时。当一个function被定义时,当前环境的scope chain会被作为function内部状态的一部分保存起来。了解了这个机制,我们就来详细分析一下scope chain的构造过程。结合如下代码。

  1. var x = "global";

  2. function f() {
  3.    var x = "local1";
  4.    function g() {
  5.       var x = "local2";
  6.       function h() {
  7.          alert(x);
  8.       }
  9.       h();
  10.    }
  11.    g();
  12. }

  13. f(); // Calling this function displays "local"

  14. var y = “global2”;

 

  • 代码开始运行的时候,Js就建立了一个context,我们可以把它叫做global context,对应的就拥有了一个scope chain,该scope chain中只包含一个元素——global object。
  • 代码运行至第3行,定义“f”函数,global context中的这个scope chain被保存进函数“f”的某个内部状态变量。
  • 代码运行到第15行,执行“f”函数,此时Js解释器又为这个函数创建了一个执行环境,还有一个call object,“f”由状态变量中读出scope chain,并将自己的scope设置为该scope chain,然后把它的call object插入到chain的首位,此时函数f的scope chain中就包含了两个object。此时,f中的嵌套function g 执行真正的定义操作。
  • g被定义时,定义所在环境中那个包含两个object的scope chain就被保存进函数“g”的某个内部状态变量。
  • 运行到第12行代码,进行与函数f执行时的相同操作,nested function g的scope chain依次包含g的call object,f的call object和global object。
  • 最后一直运行到nested function h,它的scope chain中就是h的call object,g的call object,f的call object和global object。

     经分析可知,containing function的变量会随着call object传入nested function中,因此,nested function可以任意取用和修改containing function的变量和arguments。需要注意的是,虽然function的scope chain是固定的(即由哪些call object组成),但是那些call object中的properties却不是固定的,是动态改变的,也就是说,它所能访问到的所有properties的值都是在它被调用执行之时的实时value。如下例。因对动态实时概念的不了解而造成的错误常常出现在事件的绑定应用上,我在下一章节会详细讲述。

function f1() {
    var sum = 0;
    for (var i=0; i<arguments.length; i++) {
      sum = sum + arguments[i];
    }
    return sum;
}
function bindArguments(f) {
    var boundArgs = arguments;
    var newfunc = function() {
        var args = [];
        for(var i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
        for(var i = 0; i < arguments.length; i++) args.push(arguments[i]);

        return f.apply(this, args);
    }
    
    //change its value here    
    boundArgs = [f, 1, 2, 3];
    return newfunc;
}

function test1() {
  var result = bindArguments(f1,3,4,5,6);
  document.write(result(1,2));  
//print 9 instead of 21
}


 

 

二、闭包(closure)

1、什么是闭包

    课本中的解释:闭包,是代码与其所运行的scope共同构成的联合体。因此,理论上讲,Js中的任何一个function都是一个闭包。但是,这个闭包的概念现在多被应用在涉及nested function的情况,有时,甚至将那些在定义域之外被调用的nested function称作一个闭包。如下例:

  1. function makefunc(x) {
  2.    return function() { return x++; }
  3. }
  4. var a = [makefunc(1), makefunc(5), makefunc(9)]; 
  5. alert(a[0]()); // Displays 1
  6. alert(a[0]()); // Displays 2
  7. alert(a[1]()); // Displays 5
  8. alert(a[1]()); // Displays 6
  9. alert(a[2]()); // Displays 9
  10. alert(a[2]()); // Displays 10

     这段代码中标绿的部分是一个nested function,并在第5、6、7行被调用,这个区域是在它的定义域之外的,因此,这个nested function是一个闭包。

     我们来简单分析下这个代码产生如上结果的原因。

  1. Js解释器执行makefunc(1),产生一个对应的call object(内包含arguments:[1], x=1)
  2. 触发nested function的定义操作,将call object保存进nested function的内部状态变量,此call object将一直跟随这个新建的function,直到它被回收。
  3. 将新建的nested function返回,并指定a[0]做引用。
  4. 第一个a[0]()第一次执行nested function,修改call object中的变量x,返回 1。
  5. 第二个a[0]()第二次执行nested function,在已修改的call object基础上再次修改变量x, 返回2。

2、闭包应用

模拟私有静态变量。

      有时,程序员可能希望拥有一种变量,这种变量在整个环境中只有一个,它的值可被某function修改,每次修改就都是基于上一次修改的值。它的特点类似于Java类中的static变量。实现这种效果,我们可以有三种方式:

  • 将此变量设置成全局变量。但是这个变量只需要被一个function使用,设置为全局变量将污染全局命名空间
  • 可以将该变量设置为某function的自有属性(注意,不是local变量)
  1. uniqueInteger.counter = 0;

    function uniqueInteger() {

       return uniqueInteger.counter++;
    }
  2. document.write(uniqueInteger()); //prints 0
  3. document.write(uniqueInteger()); //prints 1
  • 应用闭包,将变量通过call object保存到被返回的nested function中
  1. uniqueID = (function() {  
  2.     var id = 0;           
  3.     return function() { return id++; }; 
  4. })();
  5. document.write(uniqueID());   //prints 0
  6. document.write(uniqueID());   //prints 1

     后两种方式的区别在于,第一种,我们可以任意的在外部操作改变uniqueInteger.counter 的值,出于安全性的考虑这显然不合标准;而第二种,变量id无法用任何外部方法修改,效果类似成为一个叫做uniqueID的类的private变量。

     其实,通过上面的例子,我们可以更形象的理解闭包的概念。包含nested function的containing function它起的一个作用就是一个空间,这个空间中包含了新定义的一些变量,这些变量的值都由空间中的nested function控制,且仅受这里面的nested function控制,因此算是个封闭的空间。containing function一旦执行,这个空间就成了一个空间实例,随着nested function被作为返回值赋给外面的某变量。以后,我们通过变量来执行nested function时,所有的操作结果都仅在那个空间实例中起作用。那个封闭的空间就是一个闭包。

     我们之前研究的闭包例子都是一个空间中包含一个nested function,下面这个例子包含两个。结果会向我们展示,这两个function会分享这个空间中的资源,并能平等的操作。

  1. function makeProperty(o, name, predicate) {
  2.     var value;  
  3.     o["get" + name] = function() { return value; };  //nested function 1
  4.     o["set" + name] = function(v) {                          //nested function 2
           
    if (predicate && !predicate(v))
  5.             throw "set" + name + ": invalid value " + v;
  6.         else
  7.             value = v;
  8.      };
  9. }
  10. var o = {}; 
  11. makeProperty(o, "Name", function(x) { return typeof x == "string"; });
  12. o.setName("Frank");  // manipulate variable value
  13. document.write(o.getName());  // Prints "Frank"
  14. o.setName(0);        // throw an exception:setName: invalid value 0

事件绑定

     在编写网页时,我们经常需要为不同的element绑定相同的事件,比如三个按钮,都绑定一个执行alert操作的onclick事件,有一种写法如下:

 

  1. function attachEvent() {
  2.    var buttons = document.getElementsById("alertButton");
  3.    for(var i=0; i<buttons.length; i++) {
  4.       buttons[i].onclick = function(){
  5.           alert(i);
  6.       }
  7.    }
  8. }
  9. attachEvent();

 

    这段代码看似没有问题,但是执行起来你会发现,所有的按钮弹出的都是“2”。这是因为绑定给button的事件函数要在button真正被click时才会执行,而此时传入nested function的call object的变量i值已经更新到2,因此此时点击所有按钮都会产生同样的结果。

    我们知道,我们之所以取到了i变化后的值,是因为我们的function是在i变化之后才执行的,但如果我们可以在取到"i"这个值时立即执行函数,那么这个值就固定了。然而,这种“立即执行”显然不适用于onclick事件的绑定函数上,但是,我们却可以把一个函数加在绑定函数的外面,把i变量的值传进这个函数中,并让它立刻执行,那这样,我们期望的效果就达到了。我们就是通过(function(){})()这种语法实现这种“立即执行”的。具体改进如下:

  1. function attachEvent() {
  2.    var buttons = document.getElementsByTagName("input");
  3.    for(var i=0; i<buttons.length; i++) {
  4.          buttons[i].onclick = (function(){
  5.              var j = i;
  6.              return function(){
  7.                 alert(j);
  8.              }
  9.          })();
  10.    }
  11. }
  12. attachEvent();

     新function中的变量j负责获取i的值,此后j就与i没有关系了(因为在js里,premitive type的传递方式是值传递),并且只受这个function中的nested function控制。为了符合面向对象编程的可复用原则,上面那个方法还可以改进为:

  1. function alertAction(letter){
  2.    return function(){
  3.       alert(letter)
  4.    }
  5. }
  6. function attachEvent() {  
  7.    var buttons = document.getElementsByTagName("input");
  8.    for(var i=0; i<buttons.length; i++) {
  9.       buttons[i].onclick = alertAction(i)
  10.    }
  11. }
  12. attachEvent();

 

分享到:
评论

相关推荐

    scope-chains-closures, Javascript作用域链和闭包 workshop.zip

    scope-chains-closures, Javascript作用域链和闭包 workshop 范围链和闭包 workshop正在启动$ npm install -g scope-chains-closures$ scope-chains-closures # or, shorter: sccjs使用箭头

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

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

    javascript-闭包详解.doc

    javascript-闭包详解.doc

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

    在JavaScript编程语言中,"继承、作用域和闭包"是三个非常重要的概念,它们构成了JavaScript面向对象特性和函数式编程的核心。在这个005课的主题中,我们将深入探讨这三个概念,理解它们如何协同工作,以及如何在...

    17-闭包和装饰器(python和linux高级编程阶段 代码和截图)

    17-闭包和装饰器(python和linux高级编程阶段 代码和截图)17-闭包和装饰器(python和linux高级编程阶段 代码和截图)17-闭包和装饰器(python和linux高级编程阶段 代码和截图)17-闭包和装饰器(python和linux高级...

    JS的作用域与闭包

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

    Javascript 闭包完整解释

    **闭包**是一个非常重要的JavaScript概念,它指的是一个函数能够记住并访问其外部作用域中的变量的能力,即使该函数在其外部作用域之外被调用也是如此。具体来说,闭包是由函数及与其相关的引用环境组合而成的一个...

    现代JavaScript高级教程:深入作用域、闭包及DOM操作

    使用场景及目标:① 深入理解JavaScript的作用域、闭包及其应用场景;② 掌握MutationObserver和requestAnimationFrame等DOM操作工具;③ 提升代码质量,编写更高效、可维护的应用程序。 其他说明:书籍内容丰富,...

    Javascript-Sandbox:JavaScript 中的作用域、闭包、面向对象和异步编程

    本篇文章将深入探讨JavaScript中的四个关键概念:作用域、闭包、面向对象(OLOO)以及异步编程,这些都是JavaScript开发者必备的知识点。 1. **作用域**: JavaScript中的作用域决定了变量的可见性和生命周期。...

    闭包作用域

    在JavaScript中,闭包(Closure)是一个非常重要的概念,它允许一个函数访问并操作其外部作用域中的变量,即使该函数在其外部作用域之外被调用。这种特性使得闭包成为一种强大的工具,能够实现诸如数据封装、私有...

    10-作用域链和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的?_For_vip_user_0011

    "作用域链和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的?" 标题“作用域链和闭包:代码中出现相同的变量,JavaScript引擎是如何选择的?”中,我们可以看到JavaScript引擎是如何选择相同的变量的。...

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

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

    05-JavaScript作用域.pdf

    JavaScript作用域是指在JavaScript代码中,变量、常量、对象和函数能够访问的范围。在编程中,变量和函数的使用都受到作用域的限制,决定了它们能够在哪些代码块中被引用。作用域有助于防止变量命名冲突,也使得程序...

    深入理解变量作用域

    本文将从JavaScript权威指南出发,深入探讨变量作用域的相关知识点,包括全局作用域、局部作用域、以及闭包等高级概念。 #### 二、全局作用域与局部作用域 1. **全局作用域** - 定义:在JavaScript中,如果一个...

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

    JavaScript是Web开发中不可或缺的一部分,它提供了丰富的特性,如作用域、闭包、对象和原型链,这些都是理解和编写高效代码的关键。以下是对这些概念的详细解释: 1. **JavaScript变量作用域** - **函数作用域**:...

    JavaScript教程--从入门到精通

    3. **作用域和闭包**:作用域决定了变量的可见性,全局作用域在整个程序中都可访问,而局部作用域仅限于函数内部。闭包是一种特殊的现象,它允许函数访问并操作其外部作用域的变量,即使在函数执行完毕后仍然保持对...

    javascript闭包的理解

    标题《JavaScript闭包的理解》涉及的知识点主要围绕JavaScript编程中的一个重要概念——闭包。...对于初学者来说,理解闭包的原理和作用域链是非常有帮助的,而随着经验的增长,闭包会成为JavaScript编程中的一把利器。

    夯实基础中篇-图解作用域链和闭包.doc

    在JavaScript编程中,作用域链和闭包是两个至关重要的概念,它们对于理解代码执行机制以及函数内部如何访问和管理变量至关重要。让我们深入探讨这两个概念。 首先,**作用域链**是JavaScript中的一种机制,它定义了...

    JavaScript中的作用域链和闭包

    在JavaScript中,作用域和闭包是两个非常重要的概念,它们对于理解和编写高效、可靠的代码至关重要。让我们深入探讨这两个主题。 首先,我们来看一下**作用域**。作用域决定了变量和函数在何处可以被访问。主要有两...

Global site tag (gtag.js) - Google Analytics