先来看一个例子:
function foo() { var a = 10; function bar() { a *= 2; return a; } return bar; } var baz = foo(); // baz is now a reference to function bar. console.log(baz()); // returns 20. console.log(baz()); // returns 40. console.log(baz()); // returns 80. var blat = foo(); // blat is another reference to bar. console.log(blat()); // returns 20, because a new copy of a is being used.
一直以来,我都是以为只有用匿名函数才能算是闭包,但是其实不一定要用匿名函数的,就是一般的函数就可以,前提是它得被包含在另一个函数中。
在foo返回后,它的作用域被保存下来了,但只有它返回的的那个函数能够访问这个作用域。在前面的示例中,baz和balt各有各的作用域及a的一个副本,而且只有他们自己能对其进行修改。
其实就是说我们对foo函数的引用的调用并不会对其他引用有任何影响。
二、封装和隐藏信息
看了上面的例子,我们可以考虑采用匿名函数来进行封装和隐藏私有变量。
var Book = function(newIsbn, newTitle, newAuthor) { // implements Publication // Private attributes. var isbn, title, author; // Private method. function checkIsbn(isbn) { //... return true; } // Privileged methods. this.getIsbn = function() { return isbn; }; this.setIsbn = function(newIsbn) { if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); isbn = newIsbn; }; this.getTitle = function() { return title; }; this.setTitle = function(newTitle) { title = newTitle || 'No title specified'; }; this.getAuthor = function() { return author; }; this.setAuthor = function(newAuthor) { author = newAuthor || 'No author specified'; }; // Constructor code. this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor);};// Public, non-privileged methods.Book.prototype = { display: function() { //... }};var mybook=new Book("myisbtn","mytittle","myauthor");console.log(mybook.getAuthor());
我们通过在构造器中用var声明了这些变量和checkIsbn函数,因此他们就变成了私有的属性。需要访问这些变量和函数的方法只需要在Book中声明即可。这些方法也被陈伟特权方法。而任何不需要访问私有属性的方法都要在Book.prototype中声明。例如display。但这里也存在个问题:就是每生成一个新的对象实例都将为每一个私有方法和特权方法生成一个新的副本。这会比其他做法耗费更多内存,因此只宜用在真正需要私有成员的场合。另外,这种模式也不适合派生子类,因为派生的子类并不能访问超类的任何私有属性和方法。故在JavaScript中用闭包实现私有成员导致派生问题被称为“继承破坏封装”。
三、改进
这里与上一种大体类似,但是也有一些重要的区别。这里私有成员和特权成员仍被声明在构造器中,但是构造器已经变成一个内嵌函数了,并且被作为包含它的函数的返回值赋给变量Book.这就是创建了一个闭包,你可以把静态的私有成员函数声明在里面。
checkIsbn函数被设置为静态方法,是因为没必要为每个实例都生成这个方法的一个副本。此外还有静态属性numBooks限制了构造器总的调用次数
var Book = (function() { // Private static attributes. var numOfBooks = 0; // Private static method. function checkIsbn(isbn) { // ... return true; } // Return the constructor. return function(newIsbn, newTitle, newAuthor) { // implements Publication // Private attributes. var isbn, title, author; // Privileged methods. this.getIsbn = function() { return isbn; }; this.setIsbn = function(newIsbn) { if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); isbn = newIsbn; }; this.getTitle = function() { return title; }; this.setTitle = function(newTitle) { title = newTitle || 'No title specified'; }; this.getAuthor = function() { return author; }; this.setAuthor = function(newAuthor) { author = newAuthor || 'No author specified'; }; // Constructor code. numOfBooks++; // Keep track of how many Books have been instantiated // with the private static attribute. if(numOfBooks > 1) throw new Error('Book: Only 1 instances of Book can be ' + 'created.'); this.setIsbn(newIsbn); this.setTitle(newTitle); this.setAuthor(newAuthor); }})();// Public static method.Book.convertToTitleCase = function(inputString) { //... console.log("convertToTitleCase");};// Public, non-privileged methods.Book.prototype = { display: function() { //... console.log("display"); }};var mybook=new Book("myisbtn","mytittle","myauthor");console.log(mybook.getAuthor()); //myauthormybook.display(); //display//mybook.convertToTitleCase(); //mybook.convertToTitleCase is not a functionvar mybook2= new Book("my2","tittle2","myauthor2");console.log(mybook2.getAuthor()); //Only 1 instances of Book can be created.