zoukankan      html  css  js  c++  java
  • Javascript设计模式(1)

    本文是学习了《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);          // 进行复制,后面可以继续加参数指定具体要从 Mixin 复制的方法
    
        var author = new Author('czs', ['js', 'nodejs'])
        var serializeStr = author.serialize()
        console.log(serializeStr)
    
        // 将某个类中指定的方法复制到其他类中,而不是整体继承过去
    
    

    注意

    转载、引用,但请标明作者和原文地址

  • 相关阅读:
    Python基础语法精讲
    使用xlwt 库将数据导入为Excel 表格
    统计指定目录下所有mp4文件的时长(包含子目录下的文件)
    获取一个目录下所有文件(完整路径,要借助os.path.join()方法)
    中国参与MOOC 建设的大学有多少所?
    蒙特卡洛方法应用
    手欠,起了一卦,看那房子的风水,悲剧了
    好无聊,大半夜地找了份传世服务端源码,编着玩
    昨晚房屋的风水
    上午,写了个小东西
  • 原文地址:https://www.cnblogs.com/CccZss/p/8490300.html
Copyright © 2011-2022 走看看