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

本文深入探讨JavaScript中的作用域(scope)与闭包(closure)机制,包括scopechain的工作原理,不同环境下变量的查找流程,以及如何利用闭包实现私有静态变量等功能。

一、作用域(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();

 

内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值