本文将从一个简单例子演绎怎么定义一个具有私有成员、静态成员的类。
好,看简单原型:
if (isbn === undefined) {
throw new Error('Book constructor requires an isbn.');
}
this.isbn = isbn;
this.title = title || 'No title specified';
this.author = author || 'No author specified';
};
Book.prototype.display = function () {
alert("isbn: " + this.isbn + ", title: " + this.title + ", author: " + this.author);
};
实例中再简单不过,不过就是定义了一个有3个参数的构造函数和一个diplay实例方法。问题在于:Book的isbn属性虽然在构造中进行了简单判断,但是这还不够。改进如下:
if (!isValidIsbn(isbn)) {
throw new Error('The isbn passed in is invalid.');
}
this.isbn = isbn;
this.title = title || 'No title specified';
this.author = author || 'No author specified';
};
Book.prototype = {
isValidIsbn: function (isbn) {
if(isbn === undefined || typeof isbn != 'string') {
return false;
}
isbn = isbn.replace(/-/, ''); // Remove dashes.
if(isbn.length != 10 && isbn.length != 13) {
return false;
}
var sum = 0;
if(isbn.length === 10) { // 10 digit ISBN.
if(!isbn.match(/^\d{9}/)) { // Ensure characters 1 through 9 are digits.
return false;
}
for(var i = 0; i < 9; i++) {
sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if(checksum === 10) checksum = 'X';
if(isbn.charAt(9) != checksum) {
return false;
}
}else { // 13 digit ISBN.
if(!isbn.match(/^\d{12}/)) { // Ensure characters 1 through 12 are digits.
return false;
}
for(var i = 0; i < 12; i++) {
sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) {
return false;
}
}
return true;
},
display: function(){
alert("isbn: " + this.isbn + ", title: " + this.title + ", author: " + this.author);
}
};
新加入的isValidIsbn实例方法主题来源于Pro Javascript Design Pattern(同时修正了里面的部分错误)。借鉴于主流OOP语言做法,我们继续也为Book类增加访问器。如下:
var Book = function (isbn, title, author) {
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
};
Book.prototype = {
isValidIsbn: function (isbn) {
if (isbn === undefined || typeof isbn != 'string') {
return false;
}
isbn = isbn.replace(/-/, ''); // Remove dashes.
if (isbn.length != 10 && isbn.length != 13) {
return false;
}
var sum = 0;
if (isbn.length === 10) { // 10 digit ISBN.
if (!isbn.match(/^\d{9}/)) { // Ensure characters 1 through 9 are digits.
return false;
}
for (var i = 0; i < 9; i++) {
sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if (checksum === 10) checksum = 'X';
if (isbn.charAt(9) != checksum) {
return false;
}
} else { // 13 digit ISBN.
if (!isbn.match(/^\d{12}/)) { // Ensure characters 1 through 12 are digits.
return false;
}
for (var i = 0; i < 12; i++) {
sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3);
}
var checksum = sum % 10;
if (isbn.charAt(12) != checksum) {
return false;
}
}
return true;
},
getIsbn: function () {
return this.isbn;
},
setIsbn: function (isbn) {
if (!this.isValidIsbn(isbn)) {
throw new Error('The isbn passed in is invalid.');
}
this.isbn = isbn;
},
getTitle: function () {
return this.title;
},
setTitle: function (title) {
this.title = title || 'No title specified';
},
getAuthor: function () {
return this.author;
},
setAuthor: function (author) {
this.author = author || 'No author specified';
},
display: function () {
alert("isbn: " + this.getIsbn() + ", title: " + this.getTitle() + ", author: " + this.getAuthor());
}
};
但是,这就够了么?实际上,我们还是可以绕过访问器,直接通过对象的属性来操作对象。比如this.isbn="abc",这显然是invalid的,但是可以赋值成功。我们要做的是让isbn、title、author这些成员私有!
好,先采用业界普遍认同的方法——将伪私有变量已下划线开头:
var Book = function (isbn, title, author) {
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
};
Book.prototype = {
_isValidIsbn: function (isbn) {
if (isbn === undefined || typeof isbn != 'string') {
return false;
}
isbn = isbn.replace(/-/, ''); // Remove dashes.
if (isbn.length != 10 && isbn.length != 13) {
return false;
}
var sum = 0;
if (isbn.length === 10) { // 10 digit ISBN.
if (!isbn.match(/^\d{9}/)) { // Ensure characters 1 through 9 are digits.
return false;
}
for (var i = 0; i < 9; i++) {
sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if (checksum === 10) checksum = 'X';
if (isbn.charAt(9) != checksum) {
return false;
}
} else { // 13 digit ISBN.
if (!isbn.match(/^\d{12}/)) { // Ensure characters 1 through 12 are digits.
return false;
}
for (var i = 0; i < 12; i++) {
sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3);
}
var checksum = sum % 10;
if (isbn.charAt(12) != checksum) {
return false;
}
}
return true;
},
getIsbn: function () {
return this._isbn;
},
setIsbn: function (isbn) {
if (!this._isValidIsbn(isbn)) {
throw new Error('The isbn passed in is invalid.');
}
this._isbn = isbn;
},
getTitle: function () {
return this._title;
},
setTitle: function (title) {
this._title = title || 'No title specified';
},
getAuthor: function () {
return this._author;
},
setAuthor: function (author) {
this._author = author || 'No author specified';
},
display: function () {
alert("isbn: " + this.getIsbn() + ", title: " + this.getTitle() + ", author: " + this.getAuthor());
}
};
这样貌似可以,至少老鸟们一般不会再Book实现外部去碰它们。但是,有没有更纯的? 有!请看:
var _isbn, _title, _author;
function isValidIsbn(isbn) {
if (isbn === undefined || typeof isbn != 'string') {
return false;
}
isbn = isbn.replace(/-/, ''); // Remove dashes.
if (isbn.length != 10 && isbn.length != 13) {
return false;
}
var sum = 0;
if (isbn.length === 10) { // 10 digit ISBN.
if (!isbn.match(/^\d{9}/)) { // Ensure characters 1 through 9 are digits.
return false;
}
for (var i = 0; i < 9; i++) {
sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if (checksum === 10) checksum = 'X';
if (isbn.charAt(9) != checksum) {
return false;
}
} else { // 13 digit ISBN.
if (!isbn.match(/^\d{12}/)) { // Ensure characters 1 through 12 are digits.
return false;
}
for (var i = 0; i < 12; i++) {
sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3);
}
var checksum = sum % 10;
if (isbn.charAt(12) != checksum) {
return false;
}
}
return true;
}
this.getIsbn = function () {
return _isbn;
}
this.setIsbn = function () {
if (!isValidIsbn(isbn)) {
throw new Error('The isbn passed in is invalid.');
}
_isbn = isbn;
};
this.getTitle = function () {
return _title;
};
this.setTitle = function () {
_title = title || 'No title specified';
};
this.getAuthor = function () {
return _author;
};
this.setAuthor = function () {
_author = author || 'No author specified';
};
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
};
Book.prototype.display = function () {
alert("isbn: " + this.getIsbn() + ", title: " + this.getTitle() + ", author: " + this.getAuthor());
};
可以看到,3个被期望为私有的属性,先不在绑定在this上了,而是通过var声明在构造器内。显然,它们真的成为私有了,你是无法通过类似于new Book()._isbn来直接访问和改写它们了。然而,这个实现也有一个明显的缺点,那就是对于每个Book实例,它们的访问器都是各有一份,当需要创建多个Book对象实例时,内存就需要被多占用一些了。不过,这对于目前主流的机器来说,可能不是一个问题。
私有成员的实现到此为止,那静态成员的实现呢?关于静态方法,前面已无声无息的有涉及和实现,这里不再累赘。但是,我这里要讲的是怎么实现涉及到内部私有成员信息的静态成员。比如,我想知道Book被实例了多少次,即想要给Book内部定制一个计数器。先看看一个demo:
var constants = {
UpperBound: 100,
LowerBound: -100
};
var instanceCount = 0;
var _Class = function (id, name) {
var _id, _name;
this.getId = function () {
return _id;
};
this.setId = function (id) {
_id = id;
};
this.getName = function () {
return _name;
};
this.setName = function (name) {
_name = name;
};
this.setId(id);
this.setName(name);
++instanceCount;
};
_Class.getUpperBound = function () {
return constants.UpperBound;
};
_Class.getLowerBound = function () {
return constants.LowerBound;
};
_Class.getInstanceCount = function () {
return instanceCount;
};
return _Class;
})();
var c1 = new Class(1, "Zhangsan");
var c2 = new Class(2, "Lisi");
alert(Class.getLowerBound());
alert(Class.getInstanceCount());
Class类共有3个静态方法:getUpperBound()、getLowerBound()、getInstanceCount(),它们均实际为Class类内部私有属性的外部访问器。缘于该私有属性的实现方式和方法的性质,我思考再三,终于自己找到了解决方案!即通过声明内部_Class类,并将它绑定静态方法,以访问闭包内变量,然后将_Class引用抛出。真的是闭包功力无敌!好,终极的Book实现如下:
var numOfBooks = 0;
function checkIsbn(isbn) {
if (isbn === undefined || typeof isbn != 'string') {
return false;
}
isbn = isbn.replace(/-/, ''); // Remove dashes.
if (isbn.length != 10 && isbn.length != 13) {
return false;
}
var sum = 0;
if (isbn.length === 10) { // 10 digit ISBN.
if (!isbn.match(/^\d{9}/)) { // Ensure characters 1 through 9 are digits.
return false;
}
for (var i = 0; i < 9; i++) {
sum += isbn.charAt(i) * (10 - i);
}
var checksum = sum % 11;
if (checksum === 10) checksum = 'X';
if (isbn.charAt(9) != checksum) {
return false;
}
} else { // 13 digit ISBN.
if (!isbn.match(/^\d{12}/)) { // Ensure characters 1 through 12 are digits.
return false;
}
for (var i = 0; i < 12; i++) {
sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3);
}
var checksum = sum % 10;
if (isbn.charAt(12) != checksum) {
return false;
}
}
return true;
}
var _Book = function (newIsbn, newTitle, newAuthor) {
var isbn, title, author;
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';
};
numOfBooks++;
if (numOfBooks > 50) {
throw new Error('Book: Only 50 instances of Book can be created.');
}
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
_Book.getBookTotalCount = function () {
return numOfBooks;
};
return _Book;
})();
// Public static method.
Book.convertToTitleCase = function (inputString) {
return inputString.toUpperCase();
};
// Public, non-privileged methods.
Book.prototype = {
display: function () {
alert("isbn: " + this.getIsbn() + ", title: " + this.getTitle() + ", author: " + this.getAuthor());
}
};
var b1 = new Book("9787533668293", "How Apple works", "Steve Jobs");
var b2 = new Book("7883653519", "Biography Bill Gates", "Raymond");
b1.display();
//b2.display();
//Book.convertToTitleCase("It's only a test.");
alert(Book.getBookTotalCount());
好好学习,这边文章含金量绝对很高哦。全部源码download