本文将从一个简单例子演绎怎么定义一个具有私有成员、静态成员的类。

 

好,看简单原型:

var Book = function (isbn, title, author) {
    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属性虽然在构造中进行了简单判断,但是这还不够。改进如下:

var Book = function (isbn, title, author) {
    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 Publication = new Interface("Publication", ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']);

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 Publication = new Interface("Publication", ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']);

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 Book = function (isbn, title, author) {
    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 Class = (function () {
    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 Book = (function () {         

            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