javascript组件开发之基类继承实现
上一篇文章大概的介绍了一下关于javascript组件的开发方式,这篇文章主要详细记一下基类的编写,这个基类主要是实现继承的功能
为什么要封装基类?
由于这次重构项目需要对各种组件进行封装,并且这些组件的实现方式都差不多,为了便于管理,让代码尽量统一,所以到对组件封装一个base基类(javascript没有类的概念,暂且这样叫吧),关于javascript的oo实现:可以参考这篇文章javascript oo实现;写得很赞,膜拜,我改写的这个基于John Resig的实现方式。
基类的封装方式以及继承有哪些方法?
这个在各大库的源码中都有对应的实现,比如jq里面$.extend()实现浅拷贝和深拷贝,prototype.js里面的Object.extend的实现,以及Ext.extend等,他们的实现各不相同,在这里简单的看看prototype.js的extend实现方案:
1 //Prptotype库的extend,为Object类添加静态方法, 2 Object.extend=function(destination,source) { 3 for(property in source) { 4 destination[property]=source[property]; 5 } 6 return destination; 7 } 8 //通过Object类,为每个对象,添加方法 9 Object.prototype.extend = function(object) { 10 return Object.extend.apply(this, [this, object]); 11 }
第一个函数Object.extend,可以简单的理解为对象复制.目标对象将拥有源对象的所有属性和方法
第二个函数Object.prototype.extend,在prototype对象实例化对象中加上一个extend函数,
其中精华语句Object.extend.apply(this, [this, object]); 将Object对象中的extend作为静态方法调用,
第一个参数this,指向调用对象本身,第二个参数,为一个数组,为调用对象本身和传递进来的对象参数object(一般为方法,下面有例子)
例子:
1 //定义父类father 2 function father(){} 3 father.prototype={ 4 //各种方法 5 } 6 //定义子类son 7 function son(){} 8 son.prototype=(new father()).extend({ 9 //新添加方法 10 .... 11 })
上面的方法有一些不好的地方,比如污染Object对象的属性,比如不可以(new father()).extend({},{});不可以扩展多个对象
下面看jq的实现方式:
1 jQuery.extend = jQuery.fn.extend = function() { 2 var options, name, src, copy, copyIsArray, clone, 3 target = arguments[0] || {}, 4 i = 1, 5 length = arguments.length, 6 deep = false; 7 8 // Handle a deep copy situation处理深拷贝的情况 9 if ( typeof target === "boolean" ) { 10 deep = target; 11 target = arguments[1] || {}; 12 // skip the boolean and the target跳过深拷贝boolean参数和目标参数(源对象) 13 i = 2; 14 } 15 16 // Handle case when target is a string or something (possible in deep copy)处理目标参数(源对象)不是Object或者function的情况 17 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 18 target = {}; 19 } 20 21 // extend jQuery itself if only one argument is passed如果只有目标参数(源对象)和boolean参数的处理 22 if ( length === i ) { 23 target = this; 24 --i; 25 } 26 27 for ( ; i < length; i++ ) { 28 // Only deal with non-null/undefined values处理不为空和不为undefined的值 29 if ( (options = arguments[ i ]) != null ) { 30 // Extend the base object开始向源对象扩展 31 for ( name in options ) { 32 src = target[ name ]; 33 copy = options[ name ]; 34 35 // Prevent never-ending loop如果源对象中map到拷贝对象key的value则跳出此次循环 36 if ( target === copy ) { 37 continue; 38 } 39 40 // Recurse if we're merging plain objects or arrays 处理copy是对象和数组的情况并且是深拷贝 41 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 42 if ( copyIsArray ) { 43 copyIsArray = false; 44 clone = src && jQuery.isArray(src) ? src : []; 45 46 } else { 47 clone = src && jQuery.isPlainObject(src) ? src : {}; 48 } 49 50 // Never move original objects, clone them 51 target[ name ] = jQuery.extend( deep, clone, copy ); 52 53 // Don't bring in undefined values处理value不是对象和数组的情况 54 } else if ( copy !== undefined ) { 55 target[ name ] = copy; 56 } 57 } 58 } 59 } 60 61 // Return the modified object 62 return target; 63 };
jq的实现就避免了prototype实现的缺点,但是在我们的组件开发中不可能依赖于jq,所以下面根据John Resig的实现方式有了下面的Class超级父类,具体看注释
1 //javascript简单的实现类和继承 2 var Class = (function() { 3 //模拟extend继承方式 4 var _extend = function() { 5 //属性混入函数,不混入原型上的属性,原型上的属性就多了哈 6 var _mixProto = function(base, extend) { 7 for (var key in extend) { 8 if (extend.hasOwnProperty(key)) { 9 base[key] = extend[key]; 10 } 11 } 12 }; 13 //这里是一个开关,目的是为了在我们继承的时候不调用父类的init方法渲染,而把渲染放在子类 14 //试想没这个开关,如果在继承的时候父类有init函数就会直接渲染,而我们要的效果是继承后的子类做渲染工作 15 this.initializing = true; 16 //原型赋值 17 var prototype = new this(); 18 //开关打开 19 this.initializing = false; 20 //for循环是为了实现多个继承,例如Base.extend(events,addLog) 21 for (var i = 0,len = arguments.length; i < len; i++) { 22 //把需要继承的属性混入到父类原型 23 _mixProto(prototype, arguments[i].prototype||arguments[i]); 24 } 25 26 /*也可以这样去循环 27 var items = [].slice.call(arguments) || []; 28 var item; 29 //支持混入多个属性,并且支持{}也支持 Function 30 while (item = items.shift()) { 31 _mixProto(prototype, item.prototype || item); 32 } 33 */ 34 35 //继承后返回的子类 36 function SonClass() { 37 //检测开关和init函数的状态,看是否做渲染动作 38 if (!SonClass.initializing && this.init) 39 //这里相当于是一个虚函数,--在传统面向对象语言中,抽象类中的虚方法必须先被声明,但可以在其他方法中被调用。而在JavaScript中,虚方法 就可以看作该类中没有定义的方法,但已经通过this指针使用了。和传统面向对象不同的是,这里虚方法不需经过声明,而直接使用了。这些方法将在派生类(子类)中 实现调用返回的子类的init方法渲染,把传给组件的配置参数传给init方法,这里的apply主要是为了传参而非改变this指针 40 this.init.apply(this, arguments); 41 } 42 //把混入之后的属性和方法赋值给子类完成继承 43 SonClass.prototype = prototype; 44 //改变constructor引用,不认子类的构造函数将永远是Class超级父类 45 SonClass.prototype.constructor = SonClass; 46 //给子类页也添加继承方法,子类也可以继续继承 47 SonClass.extend = arguments.callee;//也可以是_extend 48 //返回子类 49 return SonClass 50 }; 51 //超级父类 52 var Class = function() {}; 53 54 Class.extend = _extend; 55 //返回超级父类 56 return Class 57 58 })();
附John Resig类的实现:
1 /* Simple JavaScript Inheritance 2 * By John Resig http://ejohn.org/ 3 * MIT Licensed. 4 */ 5 // Inspired by base2 and Prototype 6 (function(){ 7 //initializing是为了解决我们之前说的继承导致原型有多余参数的问题。当我们直接将父类的实例赋值给子类原型时。是会调用一次父类的构造函数的。所以这边会把真正的构造流程放到init函数里面,通过initializing来表示当前是不是处于构造原型阶段,为true的话就不会调用init。 8 //fnTest用来匹配代码里面有没有使用super关键字。对于一些浏览器`function(){xyz;}`会生成个字符串,并且会把里面的代码弄出来,有的浏览器就不会。`/xyz/.test(function(){xyz;})`为true代表浏览器支持看到函数的内部代码,所以用`/\b_super\b/`来匹配。如果不行,就不管三七二十一。所有的函数都算有super关键字,于是就是个必定匹配的正则。 9 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 10 11 // The base Class implementation (does nothing) 12 // 超级父类 13 this.Class = function(){}; 14 15 // Create a new Class that inherits from this class 16 // 生成一个类,这个类会具有extend方法用于继续继承下去 17 Class.extend = function(prop) { 18 //保留当前类,一般是父类的原型 19 //this指向父类。初次时指向Class超级父类 20 var _super = this.prototype; 21 22 // Instantiate a base class (but only create the instance, 23 // don't run the init constructor) 24 //开关 用来使原型赋值时不调用真正的构成流程 25 initializing = true; 26 var prototype = new this(); 27 initializing = false; 28 29 // Copy the properties over onto the new prototype 30 for (var name in prop) { 31 // Check if we're overwriting an existing function 32 //这边其实就是很简单的将prop的属性混入到子类的原型上。如果是函数我们就要做一些特殊处理 33 prototype[name] = typeof prop[name] == "function" && 34 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 35 (function(name, fn){ 36 //通过闭包,返回一个新的操作函数.在外面包一层,这样我们可以做些额外的处理 37 return function() { 38 var tmp = this._super; 39 40 // Add a new ._super() method that is the same method 41 // but on the super-class 42 // 调用一个函数时,会给this注入一个_super方法用来调用父类的同名方法 43 this._super = _super[name]; 44 45 // The method only need to be bound temporarily, so we 46 // remove it when we're done executing 47 //因为上面的赋值,是的这边的fn里面可以通过_super调用到父类同名方法 48 var ret = fn.apply(this, arguments); 49 //离开时 保存现场环境,恢复值。 50 this._super = tmp; 51 52 return ret; 53 }; 54 })(name, prop[name]) : 55 prop[name]; 56 } 57 58 // 这边是返回的类,其实就是我们返回的子类 59 function Class() { 60 // All construction is actually done in the init method 61 if ( !initializing && this.init ) 62 this.init.apply(this, arguments); 63 } 64 65 // 赋值原型链,完成继承 66 Class.prototype = prototype; 67 68 // 改变constructor引用 69 Class.prototype.constructor = Class; 70 71 // 为子类也添加extend方法 72 Class.extend = arguments.callee; 73 74 return Class; 75 }; 76 })();