1. 程式人生 > >Javascript設計模式(1)

Javascript設計模式(1)

繼承 constant string 驗證 OS 關心 弱類型 set aaa

本文是學習了《JavaScript設計模式》(謝廷晟 譯)做的學習筆記

一、JavaScript的靈活性

1. 普通 functon

    function startAnimation() {...}
    function stopAnimation() {...}

2. 類


    var Anim = function() {}   #構造函數

    # 方式一
        Anim.prototype.start = function() {...}
        Anim.prototype.stop = function() {...}

    # 方式二
        Anim
.prototype = { start: function() {...}, stop: function() {...} } # usage var myAnim = new Anim() myAnim.start() myAnim.stop()

3. 為實例創建方法


   # 方式一
    Function.prototype.method = function(name, fn) {
        this.prototype[name] = fn
    }
# usage var Anim = function() {} Anim.method(‘start‘, function(){...}) Anim.method(‘stop‘, function(){...}) # 方式二 (鏈式) Function.prototype.method = function(name, fn) { this.prototype[name] = fn return this } # usage Anim.method(‘start‘, function(){...}
) .method(‘stop‘, function(){...})

4. 弱類型語言

  • 原始數據類型按值傳遞,其他數據類型按引用傳遞
  • javascript 類型系統可以分為標準類型對象類型,進一步標準類型又可以分為原始類型和引用類型,而對象類型又可以分為內置對象類型、普通對象類型、自定義對象類型。

技術分享圖片

  1. 原始類型(值類型):
    • Undefined undefined
    • Null null
    • Boolean true
    • String ‘hello‘
    • Number 123
  2. 引用類型(對象類型):
    • Object
    • Array
    • Data
    • RegExp
    • Function

二、接口

1. JavaScript 中模擬接口

註釋描述接口

     # 只是使用註釋說明接口

     /*

     interface Composite {
        function add (child)
        function remove (clild)
        function getChild(index)
     }
     interface FormItem {
        function save ()
     }

     */

使用屬性檢查模仿接口

假如我們要創建一個叫 CompositeForm 的類,並且它有兩個方法 CompositeFormItem,我們可以在實現這個類的時候給它添加一個 implementsInterfaces 數組用於聲明該類有哪些方法或屬性,這樣我們就能在需要的地方用統一的檢測方法 implements 來檢測是否該類作者已經實現了這個方法。

     var CompositeForm = function(id, method, action) {
        this.implementsInterfaces = [‘Composite‘, ‘FormItem‘];   //它自己說它實現了
     }

    // 該方法需要傳入一個具有 Composite、FormItem 接口的對象,所以我們在裏面使用 implements 方法進行檢查
    function addForm(formInstance) {
        if(!implements(formInstance, ‘Composite‘, ‘FormItem‘)) {    
            throw new Error("Object dost not implement a require interface.");
        }
        // 檢查通過後進行其他處理
    }

    function implements(object) {
        for(var i=1; i < arguments.length; i++) {
            var interfaceName = arguments[i];
            var interfaceFound = false;
            for(var j=0; j<object.implementsInterfaces.length; j++) {
                if(object.implementsInterfaces[j] == interfaceName) {
                    interfaceFound = true;
                    break;
                }
            }
            if(!interfaceFound) {
                return false;
            }
        }
        return true
    }

    // 使用
     addForm(new CompositeForm())   //只能知道它是否‘說’自己實現了接口,並未確保類真正實現了接口

用鴨式辯型模仿接口

假如我們要實現一個 Composite 類,它繼承了兩個類的方法 Composite:[‘add‘, ‘remove‘, ‘getchild‘]FormItem:[‘save‘],那我們可以實現專門的類:Interface ,他的每個實例用於指定一些接口方法(有點像 Java 的接口類),它有一個公共的方法 ensureImplements 可以用來檢測某個對象是否有實現對應的 Interface 實例指定的接口。

   var Interface = function(name, methods) {
        if(arguments.length !== 2) {
            throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
        }
        this.name = name;
        this.methods = [];
        for(var i=0, len=methods.length; i<len; i++) {
            if(typeof methods[i] !== ‘string‘) {
                throw new Error("Interface constructor expects method names to be passed in as a string")
            }
            this.methods.push(methods[i]);
        }
   }  

   Interface.ensureImplements = function(object) {
        if(arguments.length < 2) {
            throw new Error("Function Interface.ensureImplements call with " + arguments.length + "arguments, but expected at least 2")
        }
        for(var i=1,len=arguments.length; i<len; i++) {
            var interface = arguments[i];
            if(interface.constructor !== Interface) {
                throw new Error("Function Interface.ensureImplements expects arguments two and above to be instances of Interface");
            }
            for(var j=0, methodsLen = interface.methods.length; j<methodsLen; j++) {
                var method = interface.methods[j];
                if(!object[method] || typeof object[method] !== ‘function‘) {
                   throw new Error(‘Function Interface.ensureImplements: Object does not implement the ‘+ interface.name + " interface. Method " + method + " was not found")
                }
            }
        }
     }

    // 創建兩個 Interface 實例並指定各自需要實現的接口
     var Composite = new Interface(‘Composite‘, [‘add‘, ‘remove‘, ‘getchild‘]);
     var FormItem = new Interface(‘FormItem‘, [‘save‘])


     // 使用
     
    var CompositeForm = function(id, method, action) {
        this.add = function() {}   // 這裏我們只實現了一個方法,所以會報錯
     }
     function addForm(formInstance) {
        // 這裏先檢查傳進來的對象是否實現對應的 Interface 實例所指定接口
        Interface.ensureImplements(formInstance, Composite, FormItem)

         // 通過後……
     }

     addForm(new CompositeForm())  // 調用方法

    // 它只關心方法的名稱,並不檢查其參數的名稱、數目或類別

其他方式

  • TypeScript :可以檢查函數參數,對類成員可以顯式聲明訪問級別:public、protected、private 等

三、封裝和信息隱藏

1. 創建對象的基本模式

1.1 門戶大開型對象

   var Book = function(isbn, title) {
        this.setIsbn(isbn)
       this.setTitle(title)
   }

   Book.prototype = {
    checkIsbn: function(isbn) {...},
       
       getIsbn: function(isbn) { return this.isbn },
       setIsbn: function(isbn) {
            if(!this.checkIsbn(isbn)) throw new Error(‘Book: Invalid ISBN‘)
           this.isbn = isbn
       },
         
       getTitle: function() { return this.title },
       setTitle: function(title) {
            this.title = title || ‘No title specified‘  //驗證
       }
   }
     
   // 雖然設置了賦值器,但是屬性依然是公開的,可以被直接賦值
   // 如 var b = new Book(‘aa‘, ‘title1‘)  可直接通過 b.isbn 修改 isbn

1.2 用命名規範區別私有成員

   var Book = function(isbn, title) {
        this.setIsbn(isbn)
       this.setTitle(title)
   }

   Book.prototype = {
    _checkIsbn: function(isbn) {...},
       
       getIsbn: function(isbn) { 
            return this._isbn 
       },
       setIsbn: function(isbn) {
            if(!this._checkout(isbn)) throw new Error(‘Book: Invalid ISBN‘)
           this._isbn = isbn
       },
         
       getTitle: function() { 
            return this._title 
       },
       setTitle: function(title) {
            this._title = title || ‘No title specified‘  //驗證
       }
   }
     
   // 下劃線只能防止程序員無意間使用某個屬性,卻不能防止對它的有意使用

?

1.3 作用域、嵌套函數和閉包

   function foo() {
        var a = 10;
    return function bar() {
            a *= 2;
            return a;
       }
   }

   #usage
   var a = foo()
   var b = foo()
   a()      // 20
   a()  // 40
   b()  // 20

1.4 用閉包實現私有成員

   var Book = function(newIsbn, newTitel) {
       var isbn, title, author;          // Private attrbute 【私有屬性】

       function checkIsbn(isbn) { return true}    // Private methods 【私有方法】

       this.getIsbn = function() {       // Privileged methods 【特權方法】
           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(newTitel) {
           title = newTitel || ‘No title specified‘  //驗證
       }

       this.setIsbn(newIsbn)
       this.setTitle(newTitel)
   }

   Book.prototype = {
       display: function() {}      //不需要訪問私有屬性的方法
   }
   // usage
   var b1 = new Book(‘11111‘, ‘title1‘)

   // 每生成一個新的對象實例都將為每個私用方法和特權方法生成一個新的副本,這會耗費更多內存
   // 這種對象創建模式不利於派生子類,因為派生的子類不能訪問超類的任何私用屬性或方法,這種方式被稱為“繼承破壞封裝”
   // 如果創建的類以後可能會需要派生出子類,最好使用【門戶大開型】或【命名規範區別私有成員】兩種方式

2. 更多高級對象創建方式

2.1 靜態方法和屬性

   var Book = (function() {
       var numOfBooks = 0;    // Private static attributes 【靜態私有屬性】所有實例共享

       function checkIsbn(isbn) {return true}   // Private static methods【靜態私有方法】所有實例共享

       return function(newIsbn, newTitel) {
           // 單個實例獨有
           var isbn, title, author;   // Private attribute 【私有屬性】

           this.getIsbn = function() {    // Privileged methods 【私有方法】
               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(newTitel) {
               title = newTitel || ‘No title specified‘  //驗證
           }

           numOfBooks++;
           if(numOfBooks > 50) {
               throw new Error(‘Book: only 50 instances of Book can be created‘)
           }

           this.setIsbn(newIsbn)
           this.setTitle(newTitel)
       }
   })()

   Book.convertToTitleCase = function(inputstring) {  //無需創建實例即可訪問的方法
       return inputstring
   }

   Book.prototype = {     //不需要訪問私有屬性的方法
       display: function() {}
   }

   // usage
   var b1 = new Book(‘11111‘, ‘title1‘)

2.2 常量

   var Class = (function(){
     
        // private static attributes【靜態私有屬性】所有實例共享
        var constants = {
            UPPER_BOUND: 100,
            LOWER_BOUND: -100
        }
        
        var ctor = {}
        ctor.getConstant = function(name) {
            return constants[name]
        }
        
        return ctor
   })()

   # usage

   Class.getContant(‘UPPER_BOUNDA‘)

   // 創建只有取值器而沒有賦值器的私有變量來模仿常量

四、繼承

1. 類式繼承

1.1 原型鏈

    function Person(name) {
        this.name = name
    }
    Person.prototype.getName = function() {
        return this.name
    }

    function Author(name, books) {
        Person.call(this, name);  // 將 Person 中 this.xxx 復制到這裏
        this.books = books
    }

    Author.prototype = new Person();
    Author.prototype.constructor = Author;
    Author.prototype.getBooks = function() {
        return this.books
    }

    # usage
    var a1 = new Author(‘aaa‘, [‘a‘,‘b‘])

1.2 extend 函數

    function extend(subClass, superClass) {
    var F = function() {}
    F.prototype = superClass.prototype;  // 使用 F 是為了避免創建超類的新實例,superClass實例做為原型時,其屬性會在subClass的實例裏面被共享(如果是引用類型如Array,裏面的每一個項修改會導致所有實例都被改),這顯然不是我們想要的,所以應該通過 superclass.call(this, ...) 把實例的屬性直接引入到 subClass的構造函數中
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;

    /* 增強版 */
    subClass.superclass = superClass.prototype    // 可直接通過 subClass.superclass.prototype.constructor 訪問到超類的構造函數,弱化子類與超類的聯系。subClass 的實例是訪問不了 superClass 屬性的。

    // 檢查超類的 prototype.contructor 是否指向自身構造函數,如果不是則改指導超類的構造函數
    if(superClass.prototype.contructor == Object.prototype.constructor) {
            superClass.prototype.constructor = superClass;
        }
    }

    # usage
    /* Class Person */
    function Person(name) {
    this.name = name
    }
    Person.prototype.getName = function() {
    return this.name
    }

    /* Class Author */
    function Author(name, books) {
    Person.call(this, name);  // 將 Person 中 this.xxx 復制到這裏
    this.books = books
    }

    extend(Author, Person)    //需要在 prototype 上添加新方法前被調用

    Author.prototype.getBooks = function() {
    return this.books
    }

2. 原型式繼承

得益於原型鏈查找的工作機制

    function clone(o) {     // 返回以給定對象為原型的【空對象】
        function F() {}
            F.prototype = o
            return new F()
    }

    var Person = {
        name: ‘default name‘,
        getName: function() {
        return this.name
        }
    }

    var reader = clone(Person)
    alert(reader.getName())  // ‘default name‘

    reader.name = ‘John Smith‘
    alert(reader.getName())  // ‘John Smith‘

    // 原型對象 Person 為其他對象應有的模樣提供了一個原型,這正是原型式繼承這個名字的由來

對繼承而來的成員的讀和寫的不對等性

第一次讀 name 時是讀到 Person 上的 name 屬性,而第一次寫是寫 reader.name = ‘xxx‘ 寫在 reader 上(引用類型變量還是在原型對象上進行修改,這很糟糕,需要先 reader.books= [] , reader.books.push())


    # 工廠方法輔助

    function clone(o) {
        function F() {}
        F.prototype = o
        return new F()
    }

    var CompoundObject = {};
    CompoundObject.string1 = ‘default value‘;
    CompoundObject.createChildObject = function() {      // 工廠方法
        return {
            bool: true,
            num: 10
        }
    }
    CompoundObject.childObject = CompoundObject.createChildObject()

    var c1 = clone(CompoundObject);
    c1.childObject = CompoundObject.createChildObject()
    c1.childObject.num = 5

3. 摻元類

    function argument(receivingClass, givingClass) {
        // 查看是否有其他參數用於指定復制特定方法
        if(argument[2]) {
            for(var i=2, len=arguments.length; i<len; i++) {
                receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]
            }
        }else {
        // 如果沒有則全部方法都復制過去 receivingClass
            for(methodName in givingClass.prototype) {
                if(!receivingClass.prototype[methodName]) {
                    receivingClass.prototype[methodName] = givingClass.prototype[methodName]
                }
            }
        }
    }

    /* Mixin class */
    var Mixin = function () {}         // 摻原類
    Mixin.prototype = {
        serialize: function() {
            var output = [];
            for(key in this) {
                output.push(key + ‘: ‘ + this[key])
            }
            return output.join(‘, ‘)
        }
    }

    function Author(name, books) {
        this.name = name
        this.books = books
    }

    argument(Author, Mixin);          // 進行復制

    var author = new Author(‘czs‘, [‘js‘, ‘nodejs‘])
    var serializeStr = author.serialize()
    console.log(serializeStr)

    // 將某個類中指定的方法復制到其他類中,而不是整體繼承過去

** 聲明:**

轉載、引用,但請標明作者和原文地址

Javascript設計模式(1)