作用域、嵌套函数和闭包

  在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中用闭包实现私有成员导致的派生问题被称为“继承破坏封装”

 

posted @ 2019-05-06 10:38  落叶无痕~  阅读(241)  评论(0编辑  收藏  举报