封装和信息隐藏是面向对象编程语言中的两个重要的概念,我们可以认为两者是同一事物的不同表述,信息隐藏是目的,而封装则是手段。
我们知道,在c++/Java中,有访问修饰符public, protected和private,来对变量或者方法的访问域进行设置,而在JavaScript中,并没有类似的修饰符。因此,我们必须通过模拟的方法来达到这一目的。
在讨论如何模拟private之前,有几个JavaScript的重要概念需要先了解一下: Scope, Nested Functions, Closures
我们先来看一段代码:
function foo() {
var a = 10;
function bar() {
a *= 2;
}
bar();
return a;
}
这段代码里面用到了内部函数的概念,在function foo里面包含了一个bar方法。同时,我们可以看到, a这个变量是在foo方法里面定义的,却可以被bar方法访问,此所谓closure。
如果我们换一种方式调用bar的话:
function foo() {
var a = 10;
function bar() {
a *= 2;
return a;
}
return bar;
}
var baz = foo(); // baz is now a reference to function bar.
baz(); // returns 20.
baz(); // returns 40.
baz(); // returns 80.
var blat = foo(); // blat is another reference to bar.
blat(); // returns 20, because a new copy of a is being used.
我们可以在a的scope以外通过baz方法访问到a,同时又保证了a的封装性,这个就是我们用来模拟面向对象封装的手段。
通过这个例子,我想到一个问题,对于面向对象语言如c++或是java,对象的构造函数都是没有返回值的,而在javascript中,我们没有办法限制构造函数是否返回值,那么当我们用一个有返回值的构造函数时,会出现什么样的情况呢?
function foo() {
var a = 10;
function bar() {
a *= 2;
return a;
}
bar.hello = "hello bar";
return bar;
}
foo.hello = "hello foo";
var baz = new foo();
alert(baz.hello);
打印出来的结果是Hello bar,也就是说,我们事实上创建了一个bar的对象,所以new创建的对象不是构造函数,而是构造函数返回的值。
那么如果构造函数返回的是一个基本类型呢?
function foo() {
var a = 10;
return a;
}
foo.hello = "hello foo";
var baz = new foo();
alert(baz.hello);
alert(baz);
调试结果显示,baz是个空的没有属性的对象,a的值貌似已经丢失。。。
回到我们之前的问题上来, 利用closure,我们便可以创建出具有封装性的类来:
var Book = function(newIsbn, newTitle, newAuthor) { // implements Publication
// Private attributes.
var isbn, title, author;
// Private method.
function checkIsbn(isbn) {
...
}
// 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 Book = (function() {
// Private static attributes.
var numOfBooks = 0;
// Private static method.
function checkIsbn(isbn) {
...
}
// 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 > 50) throw new Error('Book: Only 50 instances of Book can be '
+ 'created.');
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
}
})();
// Public static method.
Book.convertToTitleCase = function(inputString) {
...
};
// Public, non-privileged methods.
Book.prototype = {
display: function() {
...
}
};