• javascript面向对象包装类Class的类库解析


    javascript是个入门门槛很低的语言,甚至一个从来没有接触过javascript的技术人员,几小时内就可以写出一个简单有用的程序代码。

    但是如果因此你就下结论:javascript是门简单的语言。那你就大错特错了。想写出高性能的代码,同样需要具备一个高级程序员的基本素养。

    一个java或者c++程序员,不一定能写出高性能的javascript代码,但更容易写出高性能的javascript代码。

    javascript的简单是基于它“胸襟广阔”的包容性。它声明时,不需要指定类型,甚至可以任意的转换类型。它面向对象,却没有类(Class)的限制。它是一门崇尚自由又非常严谨的语言,如果你是一个自由主义者,那么,拥抱javascript吧!

    面向对象编程 (OOP) 是一种流行的编程方法。但javascript的OOP,较之JAVA、c++有很大的同,主要体现它的继承方式不同。javascript是基于原型PROTOTYPE继承的。所有对象都是基于原型链,最终追述到Object对象。

    这里不想讨论过多的关于javascript的继承方式和其它语言的继承方式的不同之处。主要讨论如何封装javascript的Class,以便更好的管理和维护基础代码,减少重复代码,以及更好的模块化编程。

    下面是几个github上找到的比较好的Class封装类库:

      一、MY-CLASS 

      项目地址:https://github.com/jiem/my-class

    先看基本用法:

    a、新建一个类

    (function() {
      // 新建类
      var Person = my.Class({
        // 添加静态方法
        STATIC: {
          AGE_OF_MAJORITY: 18
        },
        // 构造函数
        constructor: function(name, age) {
          this.name = name;
          this.age = age;
        },
        // 实例方法
        sayHello: function() {
          console.log('Hello from ' + this.name + '!');
        },
        // 实例方法
        drinkAlcohol: function() {
          this.age < Person.AGE_OF_MAJORITY ?
            console.log('Too young! Drink milk instead!') :
            console.log('Whiskey or beer?');
        }
    
      });
      // 暴露给命名空间
      myLib.Person = Person;
    
    })();
    
    var john = new myLib.Person('John', 16);
    john.sayHello(); //log "Hello from John!"
    john.drinkAlcohol(); //log "Too young! Drink milk instead!"

    b、继承一个类

    (function() {
    
      //Dreamer 继承 Person
      var Dreamer = my.Class(Person, {
        // 构造方法
        constructor: function(name, age, dream) {
          Dreamer.Super.call(this, name, age);
          this.dream = dream;
        },
        // 实例方法
        sayHello: function() {
          superSayHello.call(this);
          console.log('I dream of ' + this.dream + '!');
        },
        // 实例方法
        wakeUp: function() {
          console.log('Wake up!');
        }
    
      });
      // Super访问父类
      var superSayHello = Dreamer.Super.prototype.sayHello;
      // 暴露给全局命名空间
      myLib.Dreamer = Dreamer;
    
    })();
    
    var sylvester = new myLib.Dreamer('Sylvester', 30, 'eating Tweety');
    sylvester.sayHello(); //log "Hello from Sylvester! I dream of eating Tweety!"
    sylvester.wakeUp(); //log "Wake up!"

    c、给类添加新方法

    // 给myLib.Dreamer添加新方法
    my.extendClass(myLib.Dreamer, {
      // 添加静态方法
      STATIC : {
        s_dongSomeThing : function(){
            console.log("do some thing!");    
        }
      },
      // 添加实例方法
      touchTheSky: function() {
        console.log('Touching the sky');
      },
      // 添加实例方法
      reachTheStars: function() {
        console.log('She is so pretty!');
      }
    
    });

     d、实现一个类的方法

    // 声明一个新类
    myLib.ImaginaryTraveler = my.Class({
      travel: function() { console.log('Traveling on a carpet!'); },
      crossOceans: function() { console.log('Saying hi to Moby Dick!'); }
    });
    
    (function() {
    
      //Dreamer 继承 Person 实现 ImaginaryTraveler的方法
      var Dreamer = my.Class(Person, ImaginaryTraveler, {
        // 构造方法
        constructor: function(name, age, dream) {
          Dreamer.Super.call(this, name, age);
          this.dream = dream;
        }
    
       // ...
    
      });
      // 暴露给全局命名空间
      myLib.Dreamer = Dreamer;
    
    })();
    
    var aladdin = new Dreamer('Aladdin');
    aladdin instanceof Person; //true
    aladdin instanceof ImaginaryTraveler; //false
    aladdin.travel();
    aladdin.wakeUp();
    aladdin.sayHello();

    如果怕忘记new操作符

    var Person = my.Class({
    
      //you can now call the constructor with or without new
      constructor: function(name, city) {
        if (!(this instanceof Person))
          return new Person(name, city);
        this.name = name;
        this.city = citye;
      }
    
    });

    下面看一下my.class的源代码解析:

    my.Class实现思路基本是这样的,如果只有一个参数,那么声明的是一个基础类,这个参数是用来声明新类的方法和属以及构造函数。它不是继承而来,但它可以被继承。

    继承的思路,就是如果有两个参数,第一个参数做为父类被继承,第二参数用来声明新类的方法和属性以及构造函数,它同样可以被继承。

    如果有三个以上参数那么,除出第一个参数做为继承的父类,最后一个参数用声明新类的方法和属性以及构造函数。中间的参数是用类来扩展新类的方法。当然也可以通过my.extendClass扩展新方法。

    同时,类库为commonJS和浏览环境都提供了支持!

    /*globals define:true, window:true, module:true*/
    (function () {
      // Namespace object
      var my = {};
      // 保证AMD分模块可用
      if (typeof define !== 'undefined')
        define([], function () {
          return my;
        });
      else if (typeof window !== 'undefined')
        // 保证客户端可用
        window.my = my;
      else
        // 保证后台可用
        module.exports = my;
    
      //============================================================================
      // @method my.Class
      // @params body:Object
      // @params SuperClass:function, ImplementClasses:function..., body:Object
      // @return function
      my.Class = function () {
    
        var len = arguments.length;
        var body = arguments[len - 1];    // 最后一个参数是指定本身的方法
        var SuperClass = len > 1 ? arguments[0] : null;     // 第一个参数是指继承的方法,实例和静态部分均继承
        var hasImplementClasses = len > 2;    // 如果有第三个参数,那么第二个就是implementClass,这里其实只继承实例对象
        var Class, SuperClassEmpty;
        
        // 保证构造方法
        if (body.constructor === Object) {
          Class = function() {};
        } else {
          Class = body.constructor;
          // 保证后面不覆盖constructor
          delete body.constructor;
        }
        // 处理superClass部分
        if (SuperClass) {
          // 中间件实现实例属性的继承
          SuperClassEmpty = function() {};
          SuperClassEmpty.prototype = SuperClass.prototype;
          Class.prototype = new SuperClassEmpty();    // 原型继承,解除引用
          Class.prototype.constructor = Class;    // 保证constructor
          Class.Super = SuperClass;    // 父对象访问接口
          
          // 静态方法继承,重载superClass方法
          extend(Class, SuperClass, false);
        }
        
        // 处理ImplementClass部分,其实只继承实例属性部分,除SuperClass #arguments[0]# 和 body #arguments[length-1]#
        if (hasImplementClasses)
          for (var i = 1; i < len - 1; i++)
            // implement是继承的实例属性部分, 重载父对象implementClass方法
            extend(Class.prototype, arguments[i].prototype, false);    
        
        // 处理本身声明body部分,静态要STATIC指定,实例部分要删除STATIC部分
        extendClass(Class, body);
        
        return Class;
    
      };
    
      //============================================================================
      // @method my.extendClass
      // @params Class:function, extension:Object, ?override:boolean=true
      var extendClass = my.extendClass = function (Class, extension, override) {
        // 静态部分继承静态部分
        if (extension.STATIC) {
          extend(Class, extension.STATIC, override);
          // 保证实例部分不继承静态方法
          delete extension.STATIC;
        }
        // 实例属性继继承实例部
        extend(Class.prototype, extension, override);
      };
    
      //============================================================================
      var extend = function (obj, extension, override) {
        var prop;
        // 其实这里的flase是表明,覆盖父对象的方法
        if (override === false) {
          for (prop in extension)
            if (!(prop in obj))
              obj[prop] = extension[prop];
        } else {
           // 这里其实不覆盖父对象的方法,包括toString
          for (prop in extension)
            obj[prop] = extension[prop];
          if (extension.toString !== Object.prototype.toString)
            obj.toString = extension.toString;
        }
      };
    
    })();

     二、KLASS

     项目地址:https://github.com/ded/klass

    先看使用方法:

    a、新建一个类

    // 声明一个类
    var Person = klass(function (name) {
      this.name = name
    })
      .statics({//静态方法
        head: ':)',
        feet: '_|_'
      })
      .methods({//实例方法
        walk: function () {}
      })

    b、继承一个类

    // SuperHuman 继承 Person
    var SuperHuman = Person.extend(function (name) {
      // 自动调用父类的构造方法
    })
      .methods({
        walk: function() {
          // 显式声明调用父类的walk方法
          this.supr()
          this.fly()
        },
    
        fly: function() {}
    
      })
    
    new SuperHuman('Zelda').walk()

    c、字面量方式声明一个类

    var Foo = klass({
      foo: 0,
      initialize: function() {
        this.foo = 1
      },
      getFoo: function () {
        return this.foo
      },
      setFoo: function (x) {
        this.foo = x
        return this.getFoo()
      }
    })

    d、实现一个类的方法

    因为有时候你可能希望覆写或者混合一个实例方法,可以这样:

    // 可以传递一个字面量去继承
    var Alien = SuperHuman.extend({
      beam: function() {
        this.supr()
        // beam into space
      }
    })
    
    var Spazoid = new Alien('Zoopo')
    
    if (beamIsDown) {
      // 覆写beam方法
      Spazoid.implement({
        beam: function() {
          this.supr()
          // fallback to jets
          this.jets()
        }
      })
    }

    下面看一下klass源代码解析:

    klass的基本设计思路很明确,极力的模仿其它语言的继承方式。比如:子类构造方法调用父类的构造方法,还可以显式的声明调用父类的方法。

    这种判断都是基于正则匹配:fnTest = /xyz/.test(function () {xyz;}) ? /\bsupr\b/ : /.*/;关键字"super"

    如果显示的声明了要调用父类的方法,那么声明方法的时候,就包装成一个内部调用父类方法且返回相同值的函数,给当前类的方法。

    另一方面,构造方法,也是比较灵活的。如果显示的声明了initialize,那么这就是构造方法。否则如果参数是个function那么它就做为构造方法,否则就用父类的构造方法。

    通过statics方式添加静态方法,通过实例的implements和静态方法methods添加实例方法。

    通过父类的extend实现继承。

    同时,类库为commonJS和浏览环境都提供了支持!

    /**
      * Klass.js - copyright @dedfat
      * version 1.0
      * https://github.com/ded/klass
      * Follow our software http://twitter.com/dedfat :)
      * MIT License
      */
    !function (context, f) {
      // fnTest用来验证是否可能通过正则找出调用super父类方法的方法
      var fnTest = /xyz/.test(function () {xyz;}) ? /\bsupr\b/ : /.*/,
          noop = function (){},
          proto = 'prototype',
          isFn = function (o) {
            return typeof o === f;
          };
      // 基础类
      function klass(o) {
        return extend.call(typeof o == f ? o : noop, o, 1);
      }
    
      // 包装成一个借用super同名方法的函数
      function wrap(k, fn, supr) {
        return function () {
          // 缓存原this.super
          var tmp = this.supr;
          // 暂把this.super改造成借用super的同名方法above
          // 供o里显式的声明(fnTest.text(fn)==true)要借用super的同名方法使用
          this.supr = supr[proto][k];
          // 借用执行并保存返回值
          var ret = fn.apply(this, arguments);
          // 恢复原this.super
          this.supr = tmp;
          // 返回返回值,保证wrap后的返回值跟原来一致
          return ret;
        };
      }
       // 如果o和super有同名方法,且o显式声明借用super的同名方法,就wrap成一个待执行函数供使用
       // 如果没有显式的声明借用super的同名方法,或者是o独有的方法,或者不是方法就直接用
      function process(what, o, supr) {
        for (var k in o) {
          // 如果是非继承方法,按方法注释规则执行,最终都放进what
          if (o.hasOwnProperty(k)) {
            what[k] = typeof o[k] == f
              && typeof supr[proto][k] == f
              && fnTest.test(o[k])
              ? wrap(k, o[k], supr) : o[k];
          }
        }
      }
      // 继承方法的实现,fromSub是用来控制是否继承而来,上面的klass里面fromSub是1,表明非继承而来,构造函数不借用super执行
      function extend(o, fromSub) {
        // noop做为媒介类实现原型继承的解除引用
        noop[proto] = this[proto];
        
        var supr = this,
            prototype = new noop(), // 创建实例对象供原型继承使用,解除引用
            isFunction = typeof o == f,
            _constructor = isFunction ? o : this,// 如果o是一个构造方法就用,否则由this来决定构造函数
            _methods = isFunction ? {} : o,    // 如果o是一个{...}应该用methods放到fn原型里,如果里面有initialize就是构造函数,如果o是函数就由上面_constructor决定o是构造函数
            fn = function () { // 因为kclass借助了kclass,所以最终实际上返回的就是fn,fn其实就新类的构造函数
              
              //1 如果o是{...}就会被methods直接过滤并添加到fn的原型里,如果o里面有initialize,那么fn的原型里就有initialize,那么它就是构造方法
              //2 如果o是function,methods什么也添加不到fn的原型里,但是_constructor会接受o当构造函数
              //3 如果o是{....},同时里面也没有initialize,那么就是this当构造函数,如果在klass里由call决定,显然构造函数是noop,如果在非基础类里,构造函数就是父类的构造函数
              //  由于o不是函数不会自动调用父类的构造函数,只是把父类的构造函数当做当前类的构造函数----这都是由于this的指向决定的
              console.log(this);
              if (this.initialize) {
                this.initialize.apply(this, arguments);
              } else {
                // 调用父类构造方法
                // 如上面3,o不是函数,不会调用父类的构造方法
                // 基础类无父类,不会调用父类构造方法
                fromSub || isFn(o) && supr.apply(this, arguments);
                // 调用本类构造方法
                // 参考上面2,3要么是noop要么是o
                console.log(_constructor==noop);
                _constructor.apply(this, arguments);
              }
            };
        // 构造原型方法的接口
        fn.methods = function (o) {
          process(prototype, o, supr);
          fn[proto] = prototype;
          return this;
        };
        // 执行实现新类原型,保证新类的constructor
        fn.methods.call(fn, _methods).prototype.constructor = fn;
        // 保证新类可以被继承
        fn.extend = arguments.callee;
        // 添加实例方法或者静态方法,statics:静态方法,implement实例方法
        fn[proto].implement = fn.statics = function (o, optFn) {
          // 保证o是一个object对象,如果o是一个字符串,那么就是添一个方法的情况,如果o是一个object对象说明是批量添加的
          // 因为要从o里面拷贝
          o = typeof o == 'string' ? (function () {
            var obj = {};
            obj[o] = optFn;
            return obj;
          }()) : o;
          // 添加实例方法或者静态方法,statics:静态方法,implement实例方法
          process(this, o, supr);
          return this;
        };
    
        return fn;
      }
    
      // 后台用,nodejs
      if (typeof module !== 'undefined' && module.exports) {
        module.exports = klass;
      } else {
        
        var old = context.klass;
        // 防冲突
        klass.noConflict = function () {
          context.klass = old;
          return this;
        };
        // 前台浏览器用
        //window.kclass = kclass;
        context.klass = klass;
      }
    
    }(this, 'function');

     三、还有一种简单实现

    实现思路很简单,就是利用ECMAScript 5 原型式继承Object.create方法,封装成一个方法,如果不支持ECMAScript5的环境,就平移退化到

    function F() {};  
    F.prototype = superCtor.prototype;  
    ctor.prototype = new F();  
    ctor.prototype.constructor = ctor;

    同样的,除最后一个参数是当前类的方法声明,其它参数均做为继承父类,需要循环继承,但当这里处理的相对比较简单,没涉及到覆盖。你可以自己动手添加。

    var Class = (function() {  
          
        /**  
         * Inherits function.(node.js)  
         *   
         * @param ctor subclass's constructor.  
         * @param superctor superclass's constructor.  
         */  
        var inherits = function(ctor, superCtor) { 
            // 显式的指定父类
            ctor.super_ = superCtor;  
              
            // ECMAScript 5  原型式继承并解除引用
            if (Object.create) {  
                ctor.prototype = Object.create(superCtor.prototype, {  
                    constructor: {  
                        value: ctor,  
                        enumerable: false,  
                        writable: true,  
                        configurable: true  
                    }  
                });  
            } else {  
                // 无Object.create方法的平稳退化
                function F() {};  
                F.prototype = superCtor.prototype;  
                ctor.prototype = new F();  
                ctor.prototype.constructor = ctor;  
            }  
        };  
          
        /**  
         * Class function.  
         */  
        return function() {  
            // 最后一个参数是新类方法、属性和构造函数声明
            var subClazz = arguments[arguments.length - 1] || function() {};  
            // initialize是构造函数,否构造函数就是一个空函数
            var fn = subClazz.initialize == null ? function() {} : subClazz.initialize;  
            // 继承除最一个参数以的类,多继承,也可以用作扩展方法 
            for (var index = 0; index < arguments.length - 1; index++) {  
                inherits(fn, arguments[index]);  
            }  
            // 实现新类的方法
            for (var prop in subClazz) {  
                  
                if (prop == "initialize") {  
                    continue;  
                }  
                  
                fn.prototype[prop] = subClazz[prop];  
            }  
            
            return fn;  
        }  
          
    })(); 

    看下面实例:

    /**  
     * The definition of Cat Class.  
     */  
    var Cat = Class({  
          
        /**  
         * Constructor.  
         *   
         * @param name Cat's name  
         */  
        initialize: function(name) {
            this.name = name;  
        },  
          
        /**  
         * Eat function.  
         */  
        eat: function() {  
            alert(this.name + " is eating fish.");  
        }  
    });  
      
    /**  
     * The definition of Black Cat Class.  
     */  
    var BlackCat = Class(Cat, {  
          
        /**  
         * Constructor.  
         *   
         * @param name Cat's name.  
         * @param age Cat's age.  
         */  
        initialize: function(name, age) {  
            // call the constructor of super class.  
            BlackCat.super_.call(this, name);  
            this.age = age;  
              
        },  
          
        /**  
         * Eat function.  
         */  
        eat: function() {  
            alert(this.name + "(" + this.age + ") is eating dog.");  
        }  
    });  
      
    /**  
     * The definition of Black Fat Cat Class.  
     */  
    var BlackFatCat = Class(BlackCat, {  
          
        /**  
         * Constructor.  
         *   
         * @param name Cat's name.  
         * @param age Cat's age.  
         * @param weight Cat's weight.  
         */  
        initialize: function(name, age, weight) {  
            // call the constructor of super class.  
            BlackFatCat.super_.call(this, name, age);  
            this.weight = weight;  
        },  
          
        /**  
         * Eat function.  
         */  
        eat: function() {  
            alert(this.name + "(" + this.age + ") is eating dog. My weight: " + this.weight);  
        }  
    });  
      
      
    /**  
     * The definition of Dog Class.  
     */  
    var Dog = Class({});  
      
    var cat = new BlackFatCat("John", 24, "100kg");  
    cat.eat();  
      
    // true  
    alert(cat instanceof Cat);  
      
    // true  
    alert(cat instanceof BlackCat);  
      
    // true  
    alert(cat instanceof BlackFatCat);  
      
    // true  
    alert(cat.constructor === BlackFatCat);  
      
    // false  
    alert(cat instanceof Dog);  

     四、mootools类库的Class

    源码解析可以看这里:http://www.cnblogs.com/hmking/archive/2011/09/30/2196504.html

    看具体用法:

    a、新建一个类

    var Cat = new Class({
        initialize: function(name){
            this.name = name;
        }
    });
    var myCat = new Cat('Micia');
    alert(myCat.name); // alerts 'Micia'
     
    var Cow = new Class({
        initialize: function(){
            alert('moooo');
        }
    });

    b、继承的实现

    var Animal = new Class({
        initialize: function(age){
            this.age = age;
        }
    });
    var Cat = new Class({
        Extends: Animal,
        initialize: function(name, age){
            this.parent(age); // calls initalize method of Animal class
            this.name = name;
        }
    });
    var myCat = new Cat('Micia', 20);
    alert(myCat.name); // alerts 'Micia'.
    alert(myCat.age); // alerts 20.

    c、扩充类的实现

    var Animal = new Class({
        initialize: function(age){
            this.age = age;
        }
    });
    var Cat = new Class({
        Implements: Animal,
        setName: function(name){
            this.name = name
        }
    });
    var myAnimal = new Cat(20);
    myAnimal.setName('Micia');
    alert(myAnimal.name); // alerts 'Micia'.

    五、悟透javascript:语法甘露

    先看用法实例

    a、创建类

    // 创建类Person
    var Person = Class(object, {
        Create : function(name, age) {
            this.name = name;
            this.age = age;
        },
        SayHello : function() {
            alert("Hello, I'm " + this.name + ", " + this.age + " years old.");
        }
    });
    
    var BillGates = New(Person, ["Bill Gates", 53]);
    BillGates.SayHello();

    b、继承类

    // Employee继承Person
    var Employee = Class(Person, {
        Create : function(name, age, salary) {
            Person.Create.call(this, name, age);
            //调用基类的构造函数
            this.salary = salary;
        },
        ShowMeTheMoney : function() {
            alert(this.name + " $" + this.salary);
        }
    });
    
    var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]);
    SteveJobs.SayHello();
    SteveJobs.ShowMeTheMoney();

    下面是源码分析:显然,多了一个New方法,创建类和新建类的实例都被巧妙的封装了。形成了一个有意义的整体!还有一点不同的地方,所有的类都基于字面量,而不是基于函数。代码很简短,但其中原理却很丰富也很巧妙,可以细细品味一番!

    //创建类的函数,用于声明类及继承关系
    function Class(aBaseClass, aClassDefine) {
        //创建类的临时函数壳
        function class_() {
            this.Type = aBaseClass;
            //我们给每一个类约定一个Type属性,引用其继承的类
            for (var member in aClassDefine)
            this[member] = aClassDefine[member];
            //复制类的全部定义到当前创建的类
        };
        class_.prototype = aBaseClass;
        return new class_();
    };
    
    //创建对象的函数,用于任意类的对象创建
    function New(aClass, aParams) {
        //创建对象的临时函数壳
        function new_() {
            this.Type = aClass;
            //我们也给每一个对象约定一个Type属性,据此可以访问到对象所属的类
            if (aClass.Create)
                aClass.Create.apply(this, aParams);
            //我们约定所有类的构造函数都叫Create,这和DELPHI比较相似
        };
        new_.prototype = aClass;
        return new new_();
    };

    由于写的比较笼统,可能有很多地方没有解析到,也可能有不准确的地方,还望指正。

     看完上面几种解析,相信息自己也可以写出一个自己的封装类库出来,至于,怎么实现看个人喜好了。但基本的思都是一样的基于原型的继承方式和循环拷贝新方法。

  • 相关阅读:
    [导入]CodeSmith基础(六)
    [导入]CodeSmith基础(五)
    POJ 2229 Sumsets(经典2次幂和问题)
    UVa 10820 Send a Table(欧拉函数)
    UVa 571 Jugs(经典倒水问题)
    UVa 10717 Mint(LCM)
    UVa 10791 Minimum Sum LCM(素因子分解)
    汇编的艺术(01)sizeof operator
    UVa 11121 Base 2(负数进制)
    UVa 106 Fermat vs. Pythagoras(毕达哥拉斯定理)
  • 原文地址:https://www.cnblogs.com/pigtail/p/2869491.html
Copyright © 2020-2023  润新知