• 作用域、嵌套函数和闭包


      在JavaScript中,只有函数具有作用域。也就是说,在一个函数内部声明的变量在函数外部无法访问。私有属性就其本质而言就是你希望在对象外部无法访问的变量,所以为实现这种拒访性而求助于作用域这个概念是合乎情理的。定义一个函数中的变量在该函数的内嵌函数中是可以访问的。下面这个示例说明了JavaScript中作用域的特点:

    function foo(){

      var a = 10;

      fucntion bar(){

        a *= 2;

      }

      bar();

      return a;

    }

      在这个示例中,a定义在函数foo中,但函数bar可以访问它,因为bar也定义在foo中。bar在执行过程中将a设置为  a * 2 。当bar在foo中被调用时它能够访问a,这可以理解。但是如果bar是在foo外部被调用的呢?

    function foo(){

      var a = 10;

      function bar(){

        a *= 2;

        return a;

      }

      return bar;

    }

    var baz = foo();

    baz();  // 20

    baz();  // 40

    baz();  // 80

    var blat = foo();

    vlat();  // 20

      在上述代码中,所返回的对bar函数的引用被赋给变量baz。这个函数现在是在foo外部被调用,但它依然能够访问a。这是因为JavaScript中的作用域是词法性的。函数是运行在定义它们的作用域中(本例中是foo内部的作用域),而不是运行在调用它们的作用域中。只要bar被定义在foo中,它就能访问在foo中定义的所有变量,即使foo的执行已经结束。

      这就是闭包的一个例子。在foo返回后,它的作用域被保存下来,但只有它返回的那个函数能够访问这个作用域。在前面的示例中,baz和blat各有这个作用域及a的一个副本,而且只有他们自己能对其进行修改。返回一个内嵌函数是创建闭包最常用的手段。

    用闭包实现私有成员

      现在回过来看手头的问题:你需要创建一个只能在对象内部访问的变量。

      为了创建私有属性,需要在构造器函数的作用域中定义相关变量。这些变量可以被定义于该作用域的所有函数访问,包括哪些特权方法:

     1 var Book = function(newIsbn, newTitle, newAuthor){
     2     var isbn, title, author;
     3     
     4     function checkIsbn(isbn){
     5         ...
     6     }
     7     this.getIsbn = function(){
     8         return isbn;
     9     };
    10     this.setIsbn = function(newIsbn){
    11         if(!checkIsbn(newIsbn)){    //此处调用自定义的检查方法
    12             throw new Error('Book: Invalid ISBN.');
    13         }
    14         isbn = newIsbn;
    15     };
    16     this.getTitle = function(){
    17         return title;
    18     };
    19     this.setTitle = function(newTitle){
    20         title = newTitle || 'No title specified';
    21     };
    22     this.getAuthor = function(){
    23         return author;
    24     }
    25     this.setAuthor = function(newAuthor){
    26         author = newAuthor || 'No author specified';
    27     };
    28 
    29     this.setIsbn(newIsbn);
    30     this.setTitle(newTitle);
    31     this.setAuthor(newAuthor);
    32 };
    33 
    34 Book.prototype = {
    35     display: function(){
    36         ...
    37     }
    38 };

      我们用var声明这些变量,这意味着它们值存在于Book构造器中。checkIsbn函数也是用同样的方式声明的,因此成了一个私有方法。

      需要访问这些变量和函数的方法只需声明在Book中即可。这些方法被称为特权方法,因为它们是公有方法,但却能够访问私有属性和方法。为了在对象外部能访问这些特权函数,它们的前面都被加上了关键字this。因为这些方法定义域Book构造器的作用域中,所以它们能访问到私有属性。引用这些属性时并没有使用this关键字,因为它们不是公开的。所有取值器和赋值器方法都被改为不加this地直接引用这些属性。

      任何不需要直接访问私有属性的方法都可以在Book.prototype中声明。display就是这类方法中的一个。它不需要直接访问任何私有属性,因为它可以通过调用getIsbn或getTitle来进行间接访问。只有那些需要直接访问私有成员的方法才应该被设计为特权方法。但特权方法太多又会占用过多内存,因为每个对象实例都包含了所有特权方法的新副本。

      用这种方式创建的对象可以具有真正私有的属性。其他程序员不可能直接访问他们创建的Book实例的任何内部数据。由于他们不得不通过赋值器方法设置属性的值,所以属性会得到什么样的值尽在你的掌控之中。

    弊端:

    (1)在门户大开型对象创建模式中,所有方法都创建在原型对象中,因此不管生成多少对象实例,这些方法在内存中只存在一份。而采用上述方法,没生成一个新的对象实例,都将为每一个私有方法和特权方法生成一个新的副本。这会比其他做法耗费更多内存,所以只宜用在真正需要私有成员的场合。

    (2)这种对象创建模式也不利于派生子类,因为所派生出的子类不能访问超类的任何私有属性或方法。相比之下,在大多数语言中,子类都能访问超类的所有私有属性和方法。故在JavaScript中用闭包实现私有成员导致的派生问题被称为“继承破坏封装”

  • 相关阅读:
    cmcc_simplerop
    WeiFenLuo.winFormsUI.Docking.dll的使用
    MySQL转换Oracle的七大注意事项
    icsharpcode
    详细介绍IIS7基于WAS 部署WCF服务《收藏》
    Win2008 IIS7日期格式更改方法 《转》
    SVCUtil使用说明(生成代理类)《收藏》
    Oracle中的高效语句
    WCF配置文件全攻略《收藏》
    设计高效合理的MySQL查询语句
  • 原文地址:https://www.cnblogs.com/haishen/p/10818242.html
Copyright © 2020-2023  润新知