美文网首页
第七章 函数表达式(二)

第七章 函数表达式(二)

作者: 伊凡的一天 | 来源:发表于2018-05-11 12:28 被阅读14次

2. 模仿块级作用域

前面我们说到,JavaScript中没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。下面是一个例子:

function outputNumber(count){
    for (var i=0; i<count; i++){
        alert(i);
    }
    alert(i);
}

上面的代码是完全可执行的,因为在for语句中定义的变量i是出于函数作用域的,这也说明了JavaScript中没有块级作用域。另外,JavaScript从来不会在意是否多次声明了同一个变量,如果遇到这种情况(例如在for循环结束后,使用var i;再次定义一个i变量,也不会改变i的值,除非你在定义时显示的初始化了),它只会直接忽略后续的声明。

我们可以通过匿名函数来模拟块级作用域。下面是一个例子:

function outputNumber(count){
    (function(){
         for (var i=0; i<count; i++){
            alert(i);
         }
    })();
    alert(i);  //error!
}

在这个重写后的 outputNumbers()函数中,我们在 for 循环外部插入了一个私有作用域。在匿名 函数中定义的任何变量,都会在执行结束时被销毁。因此,变量 i 只能在循环中使用,使用后即被销毁。 而在私有作用域中能够访问变量 count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的 所有变量。

3. 私有变量

严格来说,JavaScript中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有变量的概念:任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数参数,局部变量和函数内部定义的其他函数。下面是一个例子:

function MyObject(){
    //私有变量
    var privateVariable  = 10;
    
    //私有函数
    function privateFunction(){
       return false;
    }

    this.publicMethod = function(){
        privateVariable  ++;
        return privateFunction();
    }
}

我们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。上面的例子定义了一个MyObject引用类型,在构造函数内部定义了所有私有变量和函数。然后,有定义了一个名为publicMethod()的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的 所有变量和函数。在创建 MyObject 的实例后,除了使用 publicMethod()这一个途径外,没有任何办法可以直接访问 privateVariable 和 privateFunction()

函数中定义特权方法也有一个缺点,那就是你必须使用构造函数模式来达到这个目的。第 6章曾经讨论 过,构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方 法就可以避免这个问题。

3.1 静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。下面是一个例子:

(function(){
     //私有变量
    var privateVariable  = 10;
    
    //私有函数
    function privateFunction(){
       return false;
    }
    
    //构造函数
    MyObject = function(){
    };
    //特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable  ++;
        return privateFunction();
    }
})();

这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。公有方法是在原型上定义的, 这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是 使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没 有在声明 MyObject 时使用 var 关键字。记住:初始化未经声明的变量,总是会创建一个全局变量。 因此,MyObject 就成了一个全局变量,能够在私有作用域之外被访问到。
但也要知道,在严格模式下给未经声明的变量赋值会导致错误。

以这种方式创建静态私有变量会因为使用原型模式而增进代码复用(函数是在对象实例间共享的),但缺点是每个实例都没有自己的私有变量(属性也在实例间共享,因此属性全部都是静态属性)。到底是使用实例变量,还是静态私有变量,终还是要视你的具体需求而定。

3.2 模块模式

前面介绍的模式都是用于为自定义类型创建私有变量和特权方法的,而模块模式主要用于为单例创建私有变量和特权方法。按照惯例,JavaScript是通过字面量来创建单例对象的:

var singleton = {
    name: "Ivan",
    getName: function(){
        return this.name;  
    }
}

使用字面量方式创建的单例对象,其属性都是公开的。因此就有了模块模式:

var singleton = function(){
    //私有变量
    var privateVariable  = 10;
    
    //私有函数
    function privateFunction(){
       return false;
    }

    return {
        publicMethod: function(){
            privateVariable++;
            return privateFunction();
        }
    }
}();

模块模式使用一个匿名函数返回一个对象。字面量对象中定义了公开属性和特权方法。这种模式在需要对单例进行某些初始化,同时又需要维护其私有 变量时是非常有用的,例如:

var application = function(){ 
   //私有变量和函数
   var components = new Array(); 

   //初始化
  components.push(new BaseComponent()); 

   //公共
   return {         
      getComponentCount : function(){
          return components.length;         
      }, 
      registerComponent : function(component){
          if (typeof component == "object"){                 
              components.push(component);             
          }         
       }     
    }; 
}(); 

有人进一步改进了模块模式,即在返回对象之前加入对其增强的代码。这种增强的模块模式适合那 些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。来看下面 的例子:

var application = function(){ 
    //私有变量和函数     
    var components = new Array(); 
 
    //初始化 
   components.push(new BaseComponent()); 
 
    //创建application 的一个局部副本 
   var app = new BaseComponent(); 
 
    //公共接口
    app.getComponentCount = function(){
        return components.length;
    }; 
 
    app.registerComponent = function(component){         
        if (typeof component == "object"){             
           components.push(component);        
        }    
     }; 
 
    //返回这个副本 
    return app;
}();

在这个重写后的应用程序(application)单例中,首先也是像前面例子中一样定义了私有变量。主 要的不同之处在于命名变量 app 的创建过程,因为它必须是 BaseComponent 的实例。这个实例实际上 是 application 对象的局部变量版。此后,我们又为 app 对象添加了能够访问私有变量的公有方法。 后一步是返回 app 对象,结果仍然是将它赋值给全局变量 application。

4. 总结

在JavaScript中,函数表达式是一种非常重要的技术,使用函数表达式无需对函数进行命名,从而实现动态编程。下面是函数表达式的一些显著特点:

  • 函数声明要求一定具有名字,而函数表达式则不需要。没有名字的函数表达式也被称为匿名函数。

当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数中的所有变量,原理如下;

  • 闭包函数的作用域链包含着自己的活动对象,包含函数的变量对象以及全局函数的变量对象。因此能够沿着作用域链访问包含函数中的变量。
  • 通常,函数的作用域及其所有变量都将在函数执行完毕后被销毁。
  • 但是当函数返回一个闭包时,这个函数的作用域会一直存在直到闭包被销毁为止。

使用闭包可以在JavaScript中模仿块级作用域,要点如下:

  • 定义并立即调用一个函数,这样即可以执行其中的代码,又不会在内存中留下该函数的引用。

闭包还可以用于在对象中创建私有变量,相关要点如下:

  • 即使 JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公 有方法可以访问在包含作用域中定义的变量。
  • 有权访问私有变量的公有方法叫做特权方法。
  • 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块 模式、增强 的模块模式来实现单例的特权方法。

相关文章

  • js - 函数表达式

    第七章 函数表达式 1. 创建函数两种方法 函数表达式特征 1,函数声明: 特征: 函数声明提升,意思执行代码前会...

  • 第七章 函数表达式

    第七章 函数表达式 问答 1. 函数声明的语法是? 2. 函数表达式(匿名函数)的语法是? 3. 以下递归语句是否...

  • javascript高级程序设计(第7章) -- 函数表达式

    第七章:函数表达式 本章内容: 函数表达式的特征 使用函数实现递归 使用闭包定义私有变量 定义函数的方式有两种,一...

  • 2020-01-09:第七章:函数表达式(闭包)

    第七章:函数表达式 函数表达式是js中一个非常强大但又让人困惑的特性,我们前面提到过,函数声明有两种方法:函数声明...

  • 第七章 Caché 函数大全 $CHAR 函数

    第七章 Caché 函数大全 $CHAR 函数 将表达式的整数值转换为相应的ASCII或Unicode字符。 大纲...

  • 《JavaScript高级程序设计》之笔记五

    第七章 函数表达式 1. 定义函数的两种方法 : 2. 递归 : 递归函数是在一个函数通过名字调用自身的情况下构成...

  • 07 | 读JavaScript 高程

    这是第七章函数表达式,这一章涉及函数预编译,闭包,作用域链内容。函数涉及内容繁多。 前情提要 02-1 | 读Ja...

  • 07-1 | 读JavaScript 高程

    这是第七章函数表达式,这一章涉及函数预编译,闭包,作用域链内容。函数涉及内容繁多。今天来看作用域链。 在 06-2...

  • 从Java到Kotlin(五)

    函数与Lambda表达式 目录 一、函数声明与调用二、参数和返回值三、单表达式函数四、函数作用域五、泛型函数六、尾...

  • kotlin中的lambda表达式

    一:什么是lambda表达式 lambda表达式:可以用来表示函数的语法糖,本质是一个匿名函数。 二:匿名函数 匿...

网友评论

      本文标题:第七章 函数表达式(二)

      本文链接:https://www.haomeiwen.com/subject/tjcfdftx.html