zoukankan      html  css  js  c++  java
  • Javascript中怎么定义类(私有成员、静态成员)?

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

    好,看简单原型:

    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

  • 相关阅读:
    python PyQt5
    传入一张图,生成它的油画版!(python实现)(转 )
    Python——画一棵漂亮的樱花树(不同种樱花+玫瑰+圣诞树喔)(转)
    Python3.7实现自动刷博客访问量(只需要输入用户id)(转)
    Python3 多线程的两种实现方式
    图片生成字符
    SqlServer性能优化 通过压缩与计算列提高性能(十一)
    json与bson的区别
    浅析Redis 和MongoDB
    Solr DocValues详解
  • 原文地址:https://www.cnblogs.com/Langzi127/p/2677089.html
Copyright © 2011-2022 走看看