Javascript 设计模式 学习篇 2

206 阅读8分钟

2.2 包装明星 -- 封装

2.2.1 创建一个类

var Book = function(id, bookname, price){
        this.id = id;
        this.bookname = bookname;
        this.price = price;
    }

    // 通过类的原型(类是一个对象,所以也有原型 prototype)上添加属性和方法
    1 
    Book.prototype.display = function() {
        // 展示这本书
    }
    2
    Book.prototype = {
        display : function(){}
    }

这样我们就把所需要的方法和属性都封装到我们抽象的 Book 类中了。我们并不能直接使用这个类, 需要借助 new 关键词 来实例化 新的对象。 使用 实例化对象的属性和方法的时候,通过点语法访问


    var book = new Book(10, 'javascript 设计模式', 50);
    console.log(book.bookname); // javascript 设计模式
    
通过this添加的属性和方法 与 prototype中添加的属性和方法有什么区别呢?

通过 this 添加的属性和方法是在当前对象上添加的,然而javascript是一种基于原型的语言,所以每 创建一个对象时,他都有一个原型prototype用于指向其继承的属性和方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时, 会通过prototype 一级一级查找到. 这样你就会发现通过 this定义的属性和方法是该对象自身拥有的,所以我们每次通过类创建一个新对象this指向的属性和方法都会得到相应的创建,而通过prototype继承的属性和方法是对每个对象通过prototype访问到,所以我们通过类创建一个新的对象时,这些属性和方法不会再次创建

那么,解析图中的constructor 又是指的什么呢?

constructor 是一个属性,当创建一个函数 或者 对象是都会为其创建一个 原型对象 prototype, 在 prototype 对象中又会像函数中创建 this 一样创建一个constructor 属性, 那么 constructor 属性指向的就是拥有整个原型对象的 函数 或 对象,例如在本例中 Book prototype 中的 constructor 属性指向的就是Book 类对象

2.2.2 这些都是我的 ———— 属性与方法封装

在JAVAscript 中没有显性的存在那么在 JAVAscript 中是如何实现的呢?

由于javascript 的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性即可创建类的私有变量和私有方法。然而在函数内部通过 this 创建的属性和方法, 在类创建对象时,每个对象都拥有一份并且可以在外部访问到。因此通过 this 创建的属性可看做是 对象共有的属性和方法, 而通过this创建的方法,不但可以访问这些对象公共属性和共有方法,而且还能访问到类(创建时)或对象自身的私有属性和私有方法, 由于这些方法权利比较大,所以我们又将它看做 特权方法。 在对象创建时使用这些特权方法我们可以初始化实例对象的一些属性,因此这些在创建对象时调用的特权方法还可以看做是 类的构造器

    //  私有属性与私有方法,特权方法,对象共有属性和对象公有方法,构造器
    var Book = function(id, name ,price){
        // 私有属性
        var num = 1;
        // 私有方法
        function checkId(){

        };

        // 特权方法
        this.getName = function(){};
        this.getPrice = function(){};
        this.setName = function(){};
        this.setPrice = function(){};

        // 对象公共属性
        this.id = id;
        // 对象公有方法
        this.copy = function(){};
        // 构造器
        this.setName(name);
        this.setPrice(price);

    }  

    // 类静态公有方法(对象不能访问)
    Book.isChinese = true;
    // 类静态公有方法(对象不能访问)
    Book.resetTime = function(){
        console.log('new TIME');
    };
    Book.prototype = {
        // 公有属性
        isJSBook : false,
        // 公有方法
        display : function(){}
    }

tip: 通过 new 关键字创建的对象的实质是对新对象this的不断赋值,并将 prototype 指向类的 prototype 所指向的对象,而类的构造函数外面通过点语法定义的属性方法是不会添加到新创建的对象上去的。因此要想在新创建的对象中使用 isChinese 上定义的方法就得通过 Book 类使用而不能通过 this,如 Book.isChinese,而类的原型prototype上定义的属性在新对象里就可以直接使用,这是因为新对象的 prototype 和类的 prototype 指向的是同一个对象

// 测试代码中

    var b = new Book(11,'javascript设计模式',50);
    console.log(b.num);  // undefined
    console.log(b.isJSBook); // false
    console.log(b.id);   // 11
    console.log(b.isChinese);  // undefined
    
真的是这样,类的私有属性 num 以及静态共有属性 isChinese 在新创建的 b 对象里是访问不到的。 而类的静态公有属性 isChinese可以通过类的自身访问但是类的静态共有属性isChinese可以通过类的自身访问

   console.log(Book.isChinese);  // true
   Book.resetTime();             // new TIME
   

2.2.3你们看不到我————闭包实现

有时候我们经常将类的静态变量通过 闭包 来实现

// 利用闭包实现
   var Book = (function(){
       // 静态私有变量
       var bookNum = 0;
       // 静态私有方法
       function checkBook(name) {}
       // 返回构造函数
       return function (newId, newName, newPrice) {
           // 私有变量
           var name, price;
           // 私有方法
           function checkId(id) {}

           // 特权方法
           this.getName = function(){};
           this.getPrice = function(){};
           this.setName = function(){};
           this.setPrice = function(){};

           // 公有属性
           this.id = newId;
           // 公有方法
           this.copy = function(){};
           bookNum++
           if( bookNum > 100)
               throw new Error('我们仅出版 100 本书.');
           // 构造器
           this.setName(name);
           this.setPrice(price);

       }
   })();

   Book.prototype = {
       // 静态共有属性
       isJSBook : false,
       // 静态公有方法
       display : function(){}
   };

你了解闭包吗?

闭包是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。我们将这个闭包作为创建对象的构造函数,这样它既是闭包又是可实例对象的函数,即可访问到类函数作用域中的变量如 bookNum 这个变量,此时就叫做静态私有变量,并且checkBook()可称为静态私有方法。当然闭包内部也有其自身的私有变量以及私有方法,如name,checkId()。但是,在闭包外部添加原型属性和方法看上去像脱离了闭包这个类,所以有时候在闭包内部实现一个完整的类然后将其返回。

举个栗子的时间到了

// 利用闭包实现
   var Book = (function(){
       // 静态私有变量
       var bookNum = 0;
       // 静态私有方法
       function checkBook(name) {}
       // 创建类
       function book(newId, newName, newPrice) {
           // 私有变量
           var name, price;
           // 私有方法
           function checkID(id) {}
           // 特权方法
           this.getName = function() {};
           this.getPrice = function() {};
           this.setName = function() {};
           this.setPrice = function() {};

           // 公有属性
           this.id = newId;
           // 公有方法
           this.copy = function(){};
           bookNum ++;
           if(bookNum > 100)
               throw new Error('我们仅出版 100 本书');
           // 构造器
           this.setName(name);
           this.setPrice(price);

       }

       // 构造原型
       _book.prototype = {
           // 静态公有属性
           isJSBook : false ,
           // 静态公有方法
           display : function(){}
       };

       // 返回类
       return _book;
   })();

这样看上去更像一个整体

2.2.4 找检察长————创建对象的安全模式

对于初学者来说,在创建对象上由于不适应这种写法,所以经常容易忘记使用new而犯错误。确实这种错误的发生是不可避免的,但是有什么好的方法吗?

em......如果你们犯错的时候有人可以实时监测不久解决了吗?快去找一位检察长把,比如 javascript 在创建对象时有一种安全模式就完全可以解决你们这类问题

举个栗子

// 图书类
    var Book = function(title, time, type) {
        this.title = title;
        this.time = time;
        this.type = type;
    }
    // 实例化一本书
    var book = Book('javascript', '2014', 'js');
    

'你猜 book 这个变量是个什么?'
'Book类的一个实例把。'为了验证自己的想法,写下了测试代码

console.log(book); // undefined

为什么会是这样呢?为什么是一个 undefined(未定义) ? 很是不理解

你可以这样试一下

    console.log(window.title);  // javascript
    console.log(window.time);   // 2014
    console.log(window.type);   // js

你现在发现问题了吗?

明明创建了一个 Book 对象,而且添加了title, time, type 3个属性,怎么会添加到 window 上去呢?而且 book 这个变量还是 undefined. 当你再去看你的代码的时候,恍然大悟。原来是忘记用 new 关键字来实例化了。可是为什么会出现这样的结果呢?

别急,首先你要明白一点, new 关键字的作用可以看做是对当前对象的 this 不停的赋值,然而在例子中没有用到new,所以呢就会直接执行这个函数,而这个函数在全局作用域中执行了。所以在全局作用域中this 指向的当前对象自然就是全局变量,在你的页面里全局变量就是 window 了,所以添加的属性自然就被添加到 window 上面了,而我们这个 book 变量最终的作用是要得到 Book 这个类(函数)的执行结果,由于函数没有return语句,这个Book类自然不会告诉book变量的执行结果了。所以就是 undefined.

哦,原来是这样子。看来创建时真是不小心啊, 可是该如何避免呢?

使用我们的安全模式啊

// 图书安全类
    var Book = function(title, time, type){
        // 判断执行过程中 this 是否是当前这个对象(如果是说明是用 new 创建的)
        if( this instanceof Book) {
            this.title = title;
            this.time = time;
            this.type = type;
            // 否则创建这个新对象
        }else{
            return new Book(title, time, type);
        }
    } 

    var book = Book('javascript', '2014', 'js');
    

好了,你可以测试一下了

    console.log(book);    // Book
    console.log(book.title);  // javascript
    console.log(book.time);   // 2014
    console.log(book.type);   // js
    
    console.log(window.title);  // undefined
    console.log(window.time);   // undefined
    console.log(window.type);   // undefined