1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @language=en
  9  * Create Example Class:
 10  * <pre>
 11  * var Bird = Hilo.Class.create({
 12  *     Extends: Animal,
 13  *     Mixes: EventMixin,
 14  *     constructor: function(name){
 15  *         this.name = name;
 16  *     },
 17  *     fly: function(){
 18  *         console.log('I am flying');
 19  *     },
 20  *     Statics: {
 21  *         isBird: function(bird){
 22  *             return bird instanceof Bird;
 23  *         }
 24  *     }
 25  * });
 26  *
 27  * var swallow = new Bird('swallow');
 28  * swallow.fly();
 29  * Bird.isBird(swallow);
 30  * </pre>
 31  * @namespace Class Class is created to aid the developer.
 32  * @static
 33  * @module hilo/core/Class
 34  */
 35 var Class = (function(){
 36 
 37 /**
 38  * @language=en
 39  * Create a class based on the parameters, properties and methods specified.
 40  * @param {Object} properties Properties and methods to create the class.
 41  * <ul>
 42  * <li><b>Extends</b> - Designed to inherit the parent class.</li>
 43  * <li><b>Mixes</b> - Specifies mixed member collection object.</li>
 44  * <li><b>Statics</b> - Static property or method specified class.</li>
 45  * <li><b>constructor</b> -  The constructor of specified class.</li>
 46  * <li>Other members of the property or method to create the class.</li>
 47  * </ul>
 48  * @returns {Object} Create classes.
 49  */
 50 var create = function(properties){
 51     properties = properties || {};
 52     var clazz = properties.hasOwnProperty('constructor') ? properties.constructor : function(){};
 53     implement.call(clazz, properties);
 54     return clazz;
 55 }
 56 
 57 /**
 58  * @language=en
 59  * @private
 60  */
 61 var implement = function(properties){
 62     var proto = {}, key, value;
 63     for(key in properties){
 64         value = properties[key];
 65         if(classMutators.hasOwnProperty(key)){
 66             classMutators[key].call(this, value);
 67         }else{
 68             proto[key] = value;
 69         }
 70     }
 71 
 72     mix(this.prototype, proto);
 73 };
 74 
 75 var classMutators = /** @ignore */{
 76     Extends: function(parent){
 77         var existed = this.prototype, proto = createProto(parent.prototype);
 78         //inherit static properites
 79         mix(this, parent);
 80         //keep existed properties
 81         mix(proto, existed);
 82         //correct constructor
 83         proto.constructor = this;
 84         //prototype chaining
 85         this.prototype = proto;
 86         //shortcut to parent's prototype
 87         this.superclass = parent.prototype;
 88     },
 89 
 90     Mixes: function(items){
 91         items instanceof Array || (items = [items]);
 92         var proto = this.prototype, item;
 93 
 94         while(item = items.shift()){
 95             mix(proto, item.prototype || item);
 96         }
 97     },
 98 
 99     Statics: function(properties){
100         mix(this, properties);
101     }
102 };
103 
104 /**
105  * @language=en
106  * @private
107  */
108 var createProto = (function(){
109     if(Object.__proto__){
110         return function(proto){
111             return {__proto__: proto};
112         }
113     }else{
114         var Ctor = function(){};
115         return function(proto){
116             Ctor.prototype = proto;
117             return new Ctor();
118         }
119     }
120 })();
121 
122 /**
123  * @language=en
124  * Mixed property or method.
125  * @param {Object} target Mixed audiences.
126  * @param {Object} source The source whose methods and properties are to be mixed. It can support multiple source parameters.
127  * @returns {Object} Mixed audiences.
128  */
129 var mix = function(target){
130     for(var i = 1, len = arguments.length; i < len; i++){
131         var source  = arguments[i], defineProps;
132         for(var key in source){
133             var prop = source[key];
134             if(prop && typeof prop === 'object'){
135                 if(prop.value !== undefined || typeof prop.get === 'function' || typeof prop.set === 'function'){
136                     defineProps = defineProps || {};
137                     defineProps[key] = prop;
138                     continue;
139                 }
140             }
141             target[key] = prop;
142         }
143         if(defineProps) defineProperties(target, defineProps);
144     }
145 
146     return target;
147 };
148 
149 try{
150     var defineProperty = Object.defineProperty,
151         defineProperties = Object.defineProperties;
152     defineProperty({}, '$', {value:0});
153 }catch(e){
154     if('__defineGetter__' in Object){
155         defineProperty = function(obj, prop, desc){
156             if('value' in desc) obj[prop] = desc.value;
157             if('get' in desc) obj.__defineGetter__(prop, desc.get);
158             if('set' in desc) obj.__defineSetter__(prop, desc.set);
159             return obj;
160         };
161         defineProperties = function(obj, props){
162             for(var prop in props){
163                 if(props.hasOwnProperty(prop)){
164                     defineProperty(obj, prop, props[prop]);
165                 }
166             }
167             return obj;
168         };
169     }
170 }
171 
172 return {create:create, mix:mix};
173 
174 })();
175